Cloudflare Workers: Edge Computing in Practice
Cloudflare Workers run your JavaScript or WASM within milliseconds of every user on Earth, across 300+ data centers. Unlike traditional serverless that runs in a single region, Workers run in the nearest PoP to the requesting user.
The Workers Execution Model
Workers use the V8 isolate model - not containers or VMs. Each request gets a fresh V8 context that starts in ~0ms (no cold starts). Memory is isolated between requests.
// workers/api.js - Basic Worker structure
export default {
async fetch(request, env, ctx) {
const url = new URL(request.url);
if (url.pathname.startsWith('/api/')) {
return handleAPI(request, env);
}
const cached = await env.CACHE.get(url.pathname, 'arrayBuffer');
if (cached) {
return new Response(cached, {
headers: { 'CF-Cache-Status': 'HIT', 'Cache-Control': 'public, max-age=3600' }
});
}
const response = await fetch(request);
ctx.waitUntil(
env.CACHE.put(url.pathname, response.clone().body, { expirationTtl: 3600 })
);
return response;
}
};
KV: Globally Replicated Key-Value Storage
Workers KV is eventually consistent (15s propagation), perfect for configuration, feature flags, and cached content:
async function getFeatureFlags(env) {
// KV reads have ~1ms latency (served from local PoP cache)
return await env.CONFIG.get('feature-flags', 'json') ?? { newCheckout: false };
}
// Write with metadata and TTL
await env.CONFIG.put('feature-flags', JSON.stringify({ newCheckout: true }), {
expirationTtl: 3600,
metadata: { updatedAt: Date.now() }
});
// List keys with prefix
const keys = await env.CONFIG.list({ prefix: 'user:', limit: 100 });
Durable Objects: Consistent State at the Edge
Durable Objects solve the CAP theorem challenge - each object runs in exactly ONE location globally, providing strong consistency:
export class RateLimiter {
constructor(state, env) { this.state = state; }
async fetch(request) {
const { userId, limit, windowSeconds } = await request.json();
const now = Date.now();
const windowStart = now - windowSeconds * 1000;
const requests = await this.state.storage.get('requests') ?? [];
const recent = requests.filter(ts => ts > windowStart);
if (recent.length >= limit) {
return Response.json({ allowed: false,
retryAfter: Math.ceil((recent[0] + windowSeconds * 1000 - now) / 1000) });
}
recent.push(now);
await this.state.storage.put('requests', recent);
return Response.json({ allowed: true, remaining: limit - recent.length });
}
}
export default {
async fetch(request, env) {
const userId = request.headers.get('X-User-Id');
const id = env.RATE_LIMITER.idFromName(userId);
const rateLimiter = env.RATE_LIMITER.get(id);
const result = await rateLimiter.fetch(new Request('http://do/', {
method: 'POST',
body: JSON.stringify({ userId, limit: 100, windowSeconds: 60 })
}));
const { allowed, retryAfter } = await result.json();
if (!allowed) return new Response('Rate limited', {
status: 429, headers: { 'Retry-After': retryAfter }
});
return handleRequest(request, env);
}
};
Queues: Reliable Background Processing
// Producer
export default {
async fetch(request, env) {
const order = await request.json();
await env.ORDER_QUEUE.send({ orderId: order.id, timestamp: Date.now() });
return Response.json({ queued: true });
}
};
// Consumer
export default {
async queue(batch, env) {
for (const message of batch.messages) {
try {
await processOrder(message.body, env);
message.ack();
} catch (error) {
message.retry({ delaySeconds: 30 });
}
}
}
};
R2: S3-Compatible Object Storage (No Egress Fees)
export default {
async fetch(request, env) {
const key = new URL(request.url).pathname.slice(1);
if (request.method === 'PUT') {
await env.BUCKET.put(key, request.body, {
httpMetadata: { contentType: request.headers.get('Content-Type') }
});
return new Response(null, { status: 201 });
}
const object = await env.BUCKET.get(key);
if (!object) return new Response('Not Found', { status: 404 });
const headers = new Headers();
object.writeHttpMetadata(headers);
return new Response(object.body, { headers });
}
};
Geographic Routing
export default {
async fetch(request, env) {
const continent = request.cf.continent;
const backends = {
'NA': 'https://us-east.api.example.com',
'EU': 'https://eu-west.api.example.com',
'AS': 'https://ap-southeast.api.example.com',
};
const backend = backends[continent] ?? backends['NA'];
return fetch(backend + new URL(request.url).pathname, {
method: request.method,
headers: {
...Object.fromEntries(request.headers),
'X-Client-Country': request.cf.country,
},
body: request.body
});
}
};
wrangler.toml Configuration
name = "my-edge-app"
main = "src/index.js"
compatibility_date = "2026-01-01"
[[kv_namespaces]]
binding = "CACHE"
id = "abc123def456"
[[r2_buckets]]
binding = "BUCKET"
bucket_name = "my-app-assets"
[[queues.producers]]
queue = "order-queue"
binding = "ORDER_QUEUE"
[durable_objects]
bindings = [{ name = "RATE_LIMITER", class_name = "RateLimiter" }]
Cost Model vs Traditional Serverless
| Cloudflare Workers | AWS Lambda | |
|---|---|---|
| Cold start | 0ms (isolates) | 100-500ms (containers) |
| Free tier | 100K req/day | 1M req/month |
| Regions | 300+ PoPs | ~30 regions |
| Max duration | 30s (paid) | 15 minutes |
Workers excel for: API gateways, A/B testing, auth edge validation, HTML streaming, and caching layers.