Upload Dewek: A Serverless DAM on Cloudflare's Edge
What Is It?
Upload Dewek is a headless Digital Asset Management API that runs entirely on Cloudflare's edge infrastructure — no servers, no container orchestration, no cold starts.
The core flow is deceptively simple:
- Client requests an upload URL from the API
- API generates a presigned S3 PUT URL pointing directly at R2
- Client uploads straight to R2 — the API never touches the payload
- After upload, a Durable Object validates and atomically records the asset metadata in D1
This eliminates the classic anti-pattern of proxying file uploads through your application server.
Architecture
sequenceDiagram
participant Client
participant Worker as Cloudflare Worker<br>(Hono)
participant R2 as R2 Bucket
participant D1 as D1 Database
participant DO as Durable Object
Client->>Worker: POST /upload/init
Worker->>R2: Generate presigned PUT URL
Worker->>D1: Record pending upload
Worker-->>Client: Return presigned URL
Client->>R2: PUT {presigned-url} (Direct)
Client->>Worker: POST /upload/confirm
Worker->>DO: Atomic state flip<br>(pending → confirmed)
DO->>D1: Update statusWhy Durable Objects for Confirmation?
The confirm step uses a Durable Object (not a plain Worker) because multiple concurrent confirmation requests for the same asset need serialization. A regular Worker is stateless — race conditions could result in duplicate asset records in D1. The DO acts as a per-asset mutex.
Key Implementation Details
Presigned URL Generation
R2's S3-compatible API supports presigned URLs natively. The signature is computed using @aws-sdk/signature-v4 running inside the Worker:
import { S3RequestPresigner } from "@aws-sdk/s3-request-presigner";
import { createRequest } from "@aws-sdk/util-create-request";
const presigner = new S3RequestPresigner({
credentials: {
accessKeyId: env.R2_ACCESS_KEY_ID,
secretAccessKey: env.R2_SECRET_ACCESS_KEY,
},
region: "auto",
sha256: Hash.bind(null, "sha256"),
});The URL expires in 15 minutes, scoped to a specific key and content-type.
On-the-Fly Image Optimization
Cloudflare Images transforms are applied at the CDN layer — no compute needed:
https://assets.dewek.id/{asset-id}?width=800&format=webp&quality=85
This is handled by a Worker Route that intercepts image requests and applies transforms before serving from R2 cache.
Design Decisions
| Decision | Rationale |
|---|---|
| Presigned PUTs over proxy | Eliminates Worker egress costs and bandwidth limits |
| D1 over KV | Relational queries for asset search/filtering |
| Durable Objects for confirm | Serialized state transitions, no race conditions |
| Hono over itty-router | Type-safe routing, middleware composability |
Current Status
Active development. Core upload/confirm/serve flow is complete. Working on:
- Webhook support for post-upload processing pipelines
- Multi-tenant workspace isolation via D1 row-level security
- CLI tool for direct integration with CI/CD pipelines