Skill v1.0.1
currentAutomated scan100/1001 files
version: "1.0.1" name: cloudflare-workers description: Auto-activates when user mentions Cloudflare Workers, edge functions, or serverless deployment. Expert in Cloudflare Workers including deployment, KV storage, and Durable Objects. category: serverless
Cloudflare Workers Skill
Expert knowledge in Cloudflare Workers, edge computing, KV storage, Durable Objects, R2, and serverless deployment patterns.
1. Worker Basics
1.1 Worker Syntax & Structure
Workers use the fetch event handler pattern to intercept and handle HTTP requests at the edge.
✅ Good: Standard fetch event handler
export default {async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {return new Response('Hello from the edge!', {status: 200,headers: { 'Content-Type': 'text/plain' },});},};
✅ Good: Module worker with typed environment
interface Env {MY_KV: KVNamespace;MY_SECRET: string;MY_DURABLE_OBJECT: DurableObjectNamespace;}export default {async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {const url = new URL(request.url);if (url.pathname === '/api/data') {const data = await env.MY_KV.get('key');return new Response(data, { status: 200 });}return new Response('Not found', { status: 404 });},};
❌ Bad: Using global state (resets between requests)
// Global state is NOT persistent across requests!let requestCount = 0; // ❌ This resets unpredictablyexport default {async fetch(request: Request): Promise<Response> {requestCount++; // ❌ Unreliable counterreturn new Response(`Count: ${requestCount}`);},};
❌ Bad: Blocking synchronous operations
export default {async fetch(request: Request): Promise<Response> {// ❌ Don't use synchronous blocking operationsconst data = someHeavyComputationSync(); // Wastes CPU timereturn new Response(data);},};
1.2 Request & Response Objects
Workers use standard Web APIs (Request/Response) for HTTP handling.
✅ Good: Parsing request data
export default {async fetch(request: Request, env: Env): Promise<Response> {const url = new URL(request.url);const method = request.method;if (method === 'POST') {const contentType = request.headers.get('Content-Type');if (contentType?.includes('application/json')) {const body = await request.json();return Response.json({ received: body }, { status: 200 });}if (contentType?.includes('application/x-www-form-urlencoded')) {const formData = await request.formData();return Response.json({ fields: Object.fromEntries(formData) });}}return new Response('Method not allowed', { status: 405 });},};
✅ Good: Setting response headers and status
export default {async fetch(request: Request): Promise<Response> {const data = { message: 'Success', timestamp: Date.now() };return new Response(JSON.stringify(data), {status: 200,headers: {'Content-Type': 'application/json','Cache-Control': 'public, max-age=300','X-Custom-Header': 'edge-response',},});},};
1.3 Routing Patterns
✅ Good: Path-based routing
export default {async fetch(request: Request, env: Env): Promise<Response> {const url = new URL(request.url);const path = url.pathname;if (path === '/') {return new Response('Home page');}if (path.startsWith('/api/')) {return handleAPI(request, env);}if (path.startsWith('/static/')) {return handleStatic(request, env);}return new Response('Not found', { status: 404 });},};async function handleAPI(request: Request, env: Env): Promise<Response> {return Response.json({ api: 'v1', status: 'ok' });}async function handleStatic(request: Request, env: Env): Promise<Response> {// Serve static assets from R2 or KVreturn new Response('Static content', { status: 200 });}
✅ Good: Using URL patterns with regex
const routes = [{ pattern: /^\/api\/users\/(\d+)$/, handler: getUserById },{ pattern: /^\/api\/posts\/([a-z0-9-]+)$/, handler: getPostBySlug },{ pattern: /^\/health$/, handler: healthCheck },];export default {async fetch(request: Request, env: Env): Promise<Response> {const url = new URL(request.url);for (const route of routes) {const match = url.pathname.match(route.pattern);if (match) {return route.handler(request, env, match);}}return new Response('Not found', { status: 404 });},};async function getUserById(request: Request,env: Env,match: RegExpMatchArray): Promise<Response> {const userId = match[1];return Response.json({ userId, name: 'John Doe' });}async function getPostBySlug(request: Request,env: Env,match: RegExpMatchArray): Promise<Response> {const slug = match[1];const post = await env.POSTS_KV.get(slug);return new Response(post || 'Not found', { status: post ? 200 : 404 });}async function healthCheck(): Promise<Response> {return Response.json({ status: 'healthy', timestamp: Date.now() });}
1.4 Local Development
Use Wrangler for local development with hot reloading.
✅ Good: Local development workflow
# Install Wrangler globallynpm install -g wrangler# Create new Worker projectwrangler init my-worker# Start local development server (port 8787 by default)wrangler dev# Start with remote resources (KV, Durable Objects)wrangler dev --remote# Start on custom portwrangler dev --port 3000# Start with live reloadwrangler dev --live-reload
✅ Good: Testing locally with curl
# Test GET requestcurl http://localhost:8787/# Test POST with JSONcurl -X POST http://localhost:8787/api/data \-H "Content-Type: application/json" \-d '{"key":"value"}'# Test with headerscurl http://localhost:8787/api/users/123 \-H "Authorization: Bearer token"
1.5 Deployment
✅ Good: Deploy to production
# Deploy Workerwrangler deploy# Deploy to specific environmentwrangler deploy --env production# Deploy with verbose loggingwrangler deploy --verbose# Tail logs after deploymentwrangler tail
✅ Good: wrangler.toml configuration
name = "my-worker"main = "src/index.ts"compatibility_date = "2024-11-01"# Routes (alternative to dashboard configuration)routes = [{ pattern = "example.com/*", zone_name = "example.com" }]# Environment variables[vars]ENVIRONMENT = "production"API_VERSION = "v1"# KV bindings[[kv_namespaces]]binding = "MY_KV"id = "abc123"# Durable Object bindings[[durable_objects.bindings]]name = "MY_DO"class_name = "MyDurableObject"script_name = "my-worker"# R2 bindings[[r2_buckets]]binding = "MY_BUCKET"bucket_name = "my-bucket"
2. KV Storage (Key-Value)
Workers KV is a global, low-latency key-value store optimized for high read volumes and infrequent writes.
2.1 KV Operations
✅ Good: Basic KV operations
interface Env {MY_KV: KVNamespace;}export default {async fetch(request: Request, env: Env): Promise<Response> {const url = new URL(request.url);const key = url.searchParams.get('key');if (!key) {return new Response('Key required', { status: 400 });}// GET: Read valueconst value = await env.MY_KV.get(key);if (value === null) {return new Response('Not found', { status: 404 });}return new Response(value, { status: 200 });},};
✅ Good: PUT with metadata and TTL
export default {async fetch(request: Request, env: Env): Promise<Response> {const { key, value, ttl } = await request.json();// Put with metadata and expirationawait env.MY_KV.put(key, value, {expirationTtl: ttl || 3600, // 1 hour defaultmetadata: {createdAt: Date.now(),version: '1.0',author: 'worker',},});return Response.json({ success: true, key });},};
✅ Good: GET with metadata
export default {async fetch(request: Request, env: Env): Promise<Response> {const key = 'user:123';// Get value with metadataconst { value, metadata } = await env.MY_KV.getWithMetadata(key);if (value === null) {return new Response('Not found', { status: 404 });}return Response.json({value,metadata, // Custom metadata attached to the key});},};
✅ Good: DELETE operation
export default {async fetch(request: Request, env: Env): Promise<Response> {const key = 'session:abc123';// Delete keyawait env.MY_KV.delete(key);return Response.json({ deleted: true, key });},};
2.2 KV Namespaces
KV data is organized into namespaces (isolated storage buckets).
✅ Good: Multiple namespaces for different data types
# wrangler.toml[[kv_namespaces]]binding = "USERS"id = "abc123"[[kv_namespaces]]binding = "SESSIONS"id = "def456"[[kv_namespaces]]binding = "CACHE"id = "ghi789"
interface Env {USERS: KVNamespace;SESSIONS: KVNamespace;CACHE: KVNamespace;}export default {async fetch(request: Request, env: Env): Promise<Response> {// Different namespaces for different purposesconst user = await env.USERS.get('user:123');const session = await env.SESSIONS.get('session:abc');const cachedData = await env.CACHE.get('api:response:xyz');return Response.json({ user, session, cachedData });},};
2.3 TTL and Expiration
✅ Good: Using TTL for automatic expiration
export default {async fetch(request: Request, env: Env): Promise<Response> {const sessionId = 'session:abc123';const sessionData = JSON.stringify({ userId: 123, loggedIn: true });// Expire after 1 hour (3600 seconds)await env.SESSIONS.put(sessionId, sessionData, {expirationTtl: 3600,});return Response.json({ message: 'Session created with 1-hour TTL' });},};
✅ Good: Using absolute expiration timestamp
export default {async fetch(request: Request, env: Env): Promise<Response> {const cacheKey = 'cache:data';const data = JSON.stringify({ result: 'cached' });// Expire at specific Unix timestamp (24 hours from now)const expirationTime = Math.floor(Date.now() / 1000) + 86400;await env.CACHE.put(cacheKey, data, {expiration: expirationTime,});return Response.json({ message: 'Cached until tomorrow' });},};
❌ Bad: Not using TTL for temporary data
// ❌ Storing temporary data without expirationawait env.SESSIONS.put('session:temp', 'data'); // Never expires, wastes storage
2.4 Bulk Operations
✅ Good: Listing keys with pagination
export default {async fetch(request: Request, env: Env): Promise<Response> {const allKeys: string[] = [];let cursor: string | undefined;// Paginate through all keysdo {const result = await env.MY_KV.list({ cursor, limit: 1000 });allKeys.push(...result.keys.map((k) => k.name));cursor = result.cursor;} while (cursor);return Response.json({ keys: allKeys, total: allKeys.length });},};
✅ Good: Listing keys with prefix filter
export default {async fetch(request: Request, env: Env): Promise<Response> {// List all user keys (e.g., "user:123", "user:456")const result = await env.USERS.list({ prefix: 'user:' });const userKeys = result.keys.map((k) => k.name);return Response.json({ users: userKeys });},};
❌ Bad: Fetching all keys at once without pagination
// ❌ Doesn't handle pagination, limited to 1000 keysconst result = await env.MY_KV.list();const keys = result.keys.map((k) => k.name); // Only gets first 1000 keys
2.5 Caching Patterns
✅ Good: Cache-aside pattern
export default {async fetch(request: Request, env: Env): Promise<Response> {const userId = 'user:123';// Try to get from cache firstlet user = await env.CACHE.get(userId);if (!user) {// Cache miss - fetch from origin/databaseuser = await fetchUserFromDatabase(userId);// Store in cache for 5 minutesawait env.CACHE.put(userId, user, { expirationTtl: 300 });}return new Response(user, { status: 200 });},};async function fetchUserFromDatabase(userId: string): Promise<string> {// Simulate database fetchreturn JSON.stringify({ id: userId, name: 'John Doe' });}
✅ Good: Cache invalidation on updates
export default {async fetch(request: Request, env: Env): Promise<Response> {const url = new URL(request.url);const userId = url.searchParams.get('userId');if (request.method === 'PUT') {const newData = await request.json();// Update databaseawait updateDatabase(userId!, newData);// Invalidate cacheawait env.CACHE.delete(`user:${userId}`);return Response.json({ message: 'User updated, cache invalidated' });}return new Response('Method not allowed', { status: 405 });},};async function updateDatabase(userId: string, data: any): Promise<void> {// Update database logic}
❌ Bad: Storing large values in KV
// ❌ KV has a 25 MB limit per keyconst largeFile = await fetch('https://example.com/large-file.zip').then(r => r.arrayBuffer());await env.MY_KV.put('file', largeFile); // ❌ May fail if > 25 MB
❌ Bad: Using KV for high-write workloads
// ❌ KV is optimized for reads, not writesfor (let i = 0; i < 10000; i++) {await env.MY_KV.put(`counter:${i}`, String(i)); // ❌ Slow, use Durable Objects instead}
2.6 KV Limits
| Limit | Free | Paid | |
|---|---|---|---|
| Reads/day | 100,000 | Unlimited | |
| Writes/day | 1,000 | Unlimited | |
| Deletes/day | 1,000 | Unlimited | |
| Lists/day | 1,000 | Unlimited | |
| Key size | 512 bytes | 512 bytes | |
| Value size | 25 MB | 25 MB | |
| Metadata size | 1024 bytes | 1024 bytes |
3. Durable Objects
Durable Objects provide strongly consistent, stateful storage at the edge with built-in coordination.
3.1 Creating Durable Object Classes
✅ Good: Basic Durable Object structure
export class Counter {private state: DurableObjectState;private count: number = 0;constructor(state: DurableObjectState, env: Env) {this.state = state;// Load persisted count from storagethis.state.blockConcurrencyWhile(async () => {const stored = await this.state.storage.get<number>('count');this.count = stored || 0;});}async fetch(request: Request): Promise<Response> {const url = new URL(request.url);if (url.pathname === '/increment') {this.count++;await this.state.storage.put('count', this.count);return Response.json({ count: this.count });}if (url.pathname === '/get') {return Response.json({ count: this.count });}return new Response('Not found', { status: 404 });}}
✅ Good: Durable Object with alarm
export class Scheduler {private state: DurableObjectState;constructor(state: DurableObjectState, env: Env) {this.state = state;}async fetch(request: Request): Promise<Response> {const { taskId, delay } = await request.json();// Schedule alarm for future executionconst alarmTime = Date.now() + delay;await this.state.storage.setAlarm(alarmTime);await this.state.storage.put('taskId', taskId);return Response.json({ scheduled: true, alarmTime });}async alarm(): Promise<void> {// Called when alarm triggersconst taskId = await this.state.storage.get<string>('taskId');console.log(`Executing task: ${taskId}`);// Perform scheduled workawait this.executeTask(taskId);}private async executeTask(taskId: string | undefined): Promise<void> {// Task execution logic}}
3.2 Instance Coordination
✅ Good: Using Durable Objects for coordination
export class RateLimiter {private state: DurableObjectState;constructor(state: DurableObjectState, env: Env) {this.state = state;}async fetch(request: Request): Promise<Response> {const { userId, limit, window } = await request.json();const key = `requests:${userId}`;const now = Date.now();// Get request timestampslet timestamps = await this.state.storage.get<number[]>(key) || [];// Filter out old timestamps outside the windowtimestamps = timestamps.filter(ts => now - ts < window);if (timestamps.length >= limit) {return Response.json({ allowed: false, remaining: 0 }, { status: 429 });}// Add current timestamptimestamps.push(now);await this.state.storage.put(key, timestamps);return Response.json({allowed: true,remaining: limit - timestamps.length,});}}
✅ Good: Accessing Durable Objects from Workers
interface Env {COUNTER: DurableObjectNamespace;RATE_LIMITER: DurableObjectNamespace;}export default {async fetch(request: Request, env: Env): Promise<Response> {// Get Durable Object instance by unique IDconst id = env.COUNTER.idFromName('global-counter');const stub = env.COUNTER.get(id);// Forward request to Durable Objectreturn stub.fetch(request);},};
✅ Good: Using unique IDs for isolation
export default {async fetch(request: Request, env: Env): Promise<Response> {const userId = 'user:123';// Each user gets their own Durable Object instanceconst id = env.RATE_LIMITER.idFromName(userId);const stub = env.RATE_LIMITER.get(id);const response = await stub.fetch('http://fake-host/check', {method: 'POST',body: JSON.stringify({ userId, limit: 100, window: 60000 }),});return response;},};
3.3 WebSocket Connections
✅ Good: WebSocket server in Durable Object
export class ChatRoom {private state: DurableObjectState;private sessions: Set<WebSocket> = new Set();constructor(state: DurableObjectState, env: Env) {this.state = state;}async fetch(request: Request): Promise<Response> {// Upgrade to WebSocketconst upgradeHeader = request.headers.get('Upgrade');if (upgradeHeader !== 'websocket') {return new Response('Expected WebSocket', { status: 400 });}const pair = new WebSocketPair();const [client, server] = Object.values(pair);// Accept WebSocket connectionthis.state.acceptWebSocket(server);this.sessions.add(server);server.addEventListener('message', (event) => {// Broadcast message to all connected clientsthis.broadcast(event.data as string);});server.addEventListener('close', () => {this.sessions.delete(server);});return new Response(null, { status: 101, webSocket: client });}private broadcast(message: string): void {for (const session of this.sessions) {try {session.send(message);} catch (err) {this.sessions.delete(session);}}}async webSocketMessage(ws: WebSocket, message: string | ArrayBuffer): Promise<void> {// Handle incoming WebSocket messagesthis.broadcast(typeof message === 'string' ? message : 'Binary message');}async webSocketClose(ws: WebSocket, code: number, reason: string): Promise<void> {this.sessions.delete(ws);}}
✅ Good: WebSocket hibernation for scalability
export class ScalableChatRoom {private state: DurableObjectState;constructor(state: DurableObjectState, env: Env) {this.state = state;// Enable WebSocket hibernation to reduce memory usagethis.state.setWebSocketAutoResponse(new WebSocketRequestResponsePair(JSON.stringify({ type: 'ping' }),JSON.stringify({ type: 'pong' })));}async fetch(request: Request): Promise<Response> {const pair = new WebSocketPair();const [client, server] = Object.values(pair);// Accept with hibernation enabledthis.state.acceptWebSocket(server);return new Response(null, { status: 101, webSocket: client });}async webSocketMessage(ws: WebSocket, message: string | ArrayBuffer): Promise<void> {// Only called when message is received (not during hibernation)const msg = typeof message === 'string' ? message : new TextDecoder().decode(message);const data = JSON.parse(msg);// Broadcast to all active WebSocketsthis.state.getWebSockets().forEach((socket) => {socket.send(JSON.stringify({ from: 'server', data }));});}}
3.4 Use Cases
✅ Good: Collaborative editing
export class Document {private state: DurableObjectState;private content: string = '';constructor(state: DurableObjectState, env: Env) {this.state = state;this.state.blockConcurrencyWhile(async () => {this.content = await this.state.storage.get<string>('content') || '';});}async fetch(request: Request): Promise<Response> {const url = new URL(request.url);if (url.pathname === '/edit') {const { operation, position, text } = await request.json();// Apply edit operationif (operation === 'insert') {this.content = this.content.slice(0, position) + text + this.content.slice(position);} else if (operation === 'delete') {this.content = this.content.slice(0, position) + this.content.slice(position + text.length);}// Persist changesawait this.state.storage.put('content', this.content);return Response.json({ success: true, content: this.content });}if (url.pathname === '/get') {return Response.json({ content: this.content });}return new Response('Not found', { status: 404 });}}
✅ Good: Global counters
export class PageViewCounter {private state: DurableObjectState;private views: Map<string, number> = new Map();constructor(state: DurableObjectState, env: Env) {this.state = state;}async fetch(request: Request): Promise<Response> {const { page } = await request.json();const currentViews = this.views.get(page) || 0;const newViews = currentViews + 1;this.views.set(page, newViews);// Persist to storageawait this.state.storage.put(`views:${page}`, newViews);return Response.json({ page, views: newViews });}}
❌ Bad: Using Durable Objects for stateless operations
// ❌ Don't use Durable Objects for simple stateless operationsexport class SimpleAPI {async fetch(request: Request): Promise<Response> {// ❌ No state needed, use regular Worker insteadreturn Response.json({ message: 'Hello' });}}
❌ Bad: Not persisting state to storage
export class BadCounter {private count: number = 0; // ❌ Only in memory, lost on restartasync fetch(request: Request): Promise<Response> {this.count++; // ❌ Not persisted to storagereturn Response.json({ count: this.count });}}
4. R2 Storage
R2 is Cloudflare's object storage for large files (images, videos, backups, etc.) with S3-compatible API.
4.1 Upload & Download
✅ Good: Upload file to R2
interface Env {MY_BUCKET: R2Bucket;}export default {async fetch(request: Request, env: Env): Promise<Response> {if (request.method === 'POST') {const formData = await request.formData();const file = formData.get('file') as File;if (!file) {return new Response('No file provided', { status: 400 });}// Upload to R2await env.MY_BUCKET.put(file.name, file.stream(), {httpMetadata: {contentType: file.type,},customMetadata: {uploadedBy: 'worker',uploadedAt: new Date().toISOString(),},});return Response.json({ success: true, filename: file.name });}return new Response('Method not allowed', { status: 405 });},};
✅ Good: Download file from R2
export default {async fetch(request: Request, env: Env): Promise<Response> {const url = new URL(request.url);const key = url.pathname.slice(1); // Remove leading slash// Get object from R2const object = await env.MY_BUCKET.get(key);if (!object) {return new Response('Object not found', { status: 404 });}// Return object with proper headersreturn new Response(object.body, {headers: {'Content-Type': object.httpMetadata?.contentType || 'application/octet-stream','Content-Length': String(object.size),'ETag': object.etag,'Cache-Control': 'public, max-age=3600',},});},};
✅ Good: Upload with custom metadata
export default {async fetch(request: Request, env: Env): Promise<Response> {const { key, content, metadata } = await request.json();await env.MY_BUCKET.put(key, content, {httpMetadata: {contentType: 'application/json',contentEncoding: 'gzip',cacheControl: 'public, max-age=31536000',},customMetadata: {version: metadata.version,author: metadata.author,tags: JSON.stringify(metadata.tags),},});return Response.json({ success: true });},};
4.2 List & Delete
✅ Good: List objects with pagination
export default {async fetch(request: Request, env: Env): Promise<Response> {const url = new URL(request.url);const prefix = url.searchParams.get('prefix') || '';const limit = parseInt(url.searchParams.get('limit') || '1000');const listed = await env.MY_BUCKET.list({prefix,limit,});const objects = listed.objects.map(obj => ({key: obj.key,size: obj.size,uploaded: obj.uploaded,etag: obj.etag,}));return Response.json({objects,truncated: listed.truncated,cursor: listed.cursor,});},};
✅ Good: Delete object
export default {async fetch(request: Request, env: Env): Promise<Response> {if (request.method === 'DELETE') {const url = new URL(request.url);const key = url.pathname.slice(1);await env.MY_BUCKET.delete(key);return Response.json({ deleted: true, key });}return new Response('Method not allowed', { status: 405 });},};
4.3 Multipart Uploads
✅ Good: Multipart upload for large files
export default {async fetch(request: Request, env: Env): Promise<Response> {const key = 'large-file.zip';// Start multipart uploadconst multipart = await env.MY_BUCKET.createMultipartUpload(key);const parts: R2UploadedPart[] = [];const chunkSize = 5 * 1024 * 1024; // 5 MB chunks// Upload parts (simulated)for (let i = 0; i < 10; i++) {const chunk = new Uint8Array(chunkSize); // Simulate chunk dataconst uploadedPart = await multipart.uploadPart(i + 1, chunk);parts.push(uploadedPart);}// Complete multipart uploadconst object = await multipart.complete(parts);return Response.json({ success: true, etag: object.etag });},};
❌ Bad: Uploading large files without multipart
// ❌ Don't upload files > 100 MB in single requestconst largeFile = new Uint8Array(200 * 1024 * 1024); // 200 MBawait env.MY_BUCKET.put('large.bin', largeFile); // ❌ May timeout or fail
4.4 Public Buckets & Presigned URLs
✅ Good: Serve public files from R2
export default {async fetch(request: Request, env: Env): Promise<Response> {const url = new URL(request.url);const key = url.pathname.slice(1);const object = await env.MY_BUCKET.get(key);if (!object) {return new Response('Not found', { status: 404 });}// Serve with caching headersreturn new Response(object.body, {headers: {'Content-Type': object.httpMetadata?.contentType || 'application/octet-stream','Cache-Control': 'public, max-age=86400','ETag': object.etag,},});},};
✅ Good: Generate presigned URL (alternative pattern)
// R2 doesn't have native presigned URLs, but you can create signed tokensexport default {async fetch(request: Request, env: Env): Promise<Response> {const key = 'private/document.pdf';const token = await generateToken(key, env.SECRET_KEY);const signedUrl = `https://my-worker.example.com/download/${key}?token=${token}`;return Response.json({ url: signedUrl });},};async function generateToken(key: string, secret: string): Promise<string> {const data = `${key}:${Date.now() + 3600000}`; // Expires in 1 hourconst encoder = new TextEncoder();const keyData = encoder.encode(secret);const algorithm = { name: 'HMAC', hash: 'SHA-256' };const cryptoKey = await crypto.subtle.importKey('raw', keyData, algorithm, false, ['sign']);const signature = await crypto.subtle.sign(algorithm.name, cryptoKey, encoder.encode(data));return btoa(String.fromCharCode(...new Uint8Array(signature)));}
❌ Bad: Exposing sensitive files without authentication
// ❌ Serving private files without access controlexport default {async fetch(request: Request, env: Env): Promise<Response> {const key = 'private/secret.pdf';const object = await env.MY_BUCKET.get(key);return new Response(object?.body); // ❌ No authentication!},};
5. Bindings
Bindings connect Workers to Cloudflare resources (KV, Durable Objects, R2, etc.).
5.1 KV Bindings
✅ Good: KV binding in wrangler.toml
[[kv_namespaces]]binding = "MY_KV"id = "abc123def456"[[kv_namespaces]]binding = "CACHE"id = "xyz789"preview_id = "preview123" # For local dev
✅ Good: Using KV bindings in TypeScript
interface Env {MY_KV: KVNamespace;CACHE: KVNamespace;}export default {async fetch(request: Request, env: Env): Promise<Response> {const data = await env.MY_KV.get('key');return new Response(data);},};
5.2 Durable Object Bindings
✅ Good: Durable Object binding
[[durable_objects.bindings]]name = "COUNTER"class_name = "Counter"script_name = "my-worker" # Optional if in same script[[migrations]]tag = "v1"new_classes = ["Counter"]
interface Env {COUNTER: DurableObjectNamespace;}export default {async fetch(request: Request, env: Env): Promise<Response> {const id = env.COUNTER.idFromName('global');const stub = env.COUNTER.get(id);return stub.fetch(request);},};export class Counter {private state: DurableObjectState;constructor(state: DurableObjectState, env: Env) {this.state = state;}async fetch(request: Request): Promise<Response> {return Response.json({ count: 123 });}}
5.3 R2 Bindings
✅ Good: R2 bucket binding
[[r2_buckets]]binding = "MY_BUCKET"bucket_name = "my-bucket-name"[[r2_buckets]]binding = "ASSETS"bucket_name = "static-assets"
interface Env {MY_BUCKET: R2Bucket;ASSETS: R2Bucket;}export default {async fetch(request: Request, env: Env): Promise<Response> {const file = await env.ASSETS.get('logo.png');return new Response(file?.body);},};
5.4 Service Bindings
Service bindings allow Workers to call other Workers directly (RPC-style).
✅ Good: Service binding configuration
# worker-a/wrangler.tomlname = "worker-a"[[services]]binding = "WORKER_B"service = "worker-b"
// worker-a/index.tsinterface Env {WORKER_B: Fetcher;}export default {async fetch(request: Request, env: Env): Promise<Response> {// Call worker-b directly (no HTTP overhead)const response = await env.WORKER_B.fetch('http://fake-host/api/data');const data = await response.json();return Response.json({ fromWorkerB: data });},};
// worker-b/index.tsexport default {async fetch(request: Request): Promise<Response> {return Response.json({ message: 'Hello from Worker B' });},};
5.5 Environment Variables & Secrets
✅ Good: Environment variables
[vars]ENVIRONMENT = "production"API_VERSION = "v1"MAX_RETRIES = "3"
interface Env {ENVIRONMENT: string;API_VERSION: string;MAX_RETRIES: string;}export default {async fetch(request: Request, env: Env): Promise<Response> {return Response.json({env: env.ENVIRONMENT,version: env.API_VERSION,});},};
✅ Good: Managing secrets with Wrangler
# Set secret (not stored in wrangler.toml)wrangler secret put API_KEY# Enter secret value when prompted# Delete secretwrangler secret delete API_KEY# List secretswrangler secret list
interface Env {API_KEY: string; // Secret from Wrangler}export default {async fetch(request: Request, env: Env): Promise<Response> {const response = await fetch('https://api.example.com/data', {headers: { 'Authorization': `Bearer ${env.API_KEY}` },});return response;},};
❌ Bad: Hardcoding secrets in code
// ❌ Never hardcode secrets!const API_KEY = 'sk-1234567890abcdef'; // ❌ Exposed in source codeexport default {async fetch(request: Request): Promise<Response> {const response = await fetch('https://api.example.com', {headers: { 'Authorization': `Bearer ${API_KEY}` },});return response;},};
6. Performance & Limits
6.1 CPU Time Limits
| Plan | CPU Time (HTTP) | CPU Time (Cron) | |
|---|---|---|---|
| Free | 10 ms | 10 ms | |
| Paid | 30 seconds | 15 minutes |
✅ Good: Efficient CPU usage
export default {async fetch(request: Request, env: Env): Promise<Response> {// Offload heavy computation to external API or Durable Objectconst result = await fetch('https://compute-service.example.com/heavy-task');return result;},};
❌ Bad: CPU-intensive operations
// ❌ Heavy computation exceeds CPU limitsexport default {async fetch(request: Request): Promise<Response> {let result = 0;for (let i = 0; i < 1_000_000_000; i++) {result += Math.sqrt(i); // ❌ Exceeds 10ms CPU limit}return Response.json({ result });},};
6.2 Memory Limits
Workers have a 128 MB memory limit.
✅ Good: Streaming large responses
export default {async fetch(request: Request, env: Env): Promise<Response> {const object = await env.MY_BUCKET.get('large-file.mp4');// Stream directly (doesn't load into memory)return new Response(object?.body, {headers: { 'Content-Type': 'video/mp4' },});},};
❌ Bad: Loading large files into memory
// ❌ Loading entire file into memoryexport default {async fetch(request: Request, env: Env): Promise<Response> {const object = await env.MY_BUCKET.get('large-file.mp4');const arrayBuffer = await object?.arrayBuffer(); // ❌ May exceed 128 MBreturn new Response(arrayBuffer);},};
6.3 Request & Response Limits
| Limit | Value | |
|---|---|---|
| Request URL | 16 KB | |
| Request headers | 128 KB | |
| Request body (Free/Pro) | 100 MB | |
| Request body (Business) | 200 MB | |
| Request body (Enterprise) | 500 MB | |
| Response headers | 128 KB | |
| Subrequests | 50 (Free), 1000 (Paid) |
✅ Good: Handling subrequest limits
export default {async fetch(request: Request, env: Env): Promise<Response> {// Batch requests to stay under subrequest limitconst urls = ['url1', 'url2', 'url3']; // Only 3 subrequestsconst responses = await Promise.all(urls.map(url => fetch(`https://api.example.com/${url}`)));const data = await Promise.all(responses.map(r => r.json()));return Response.json(data);},};
❌ Bad: Exceeding subrequest limits
// ❌ Too many subrequests on free planexport default {async fetch(request: Request): Promise<Response> {const promises = [];for (let i = 0; i < 100; i++) {promises.push(fetch(`https://api.example.com/item/${i}`)); // ❌ Exceeds 50 subrequests}await Promise.all(promises);return new Response('Done');},};
6.4 KV Limits (Repeated for Reference)
| Limit | Free | Paid | |
|---|---|---|---|
| Reads/day | 100,000 | Unlimited | |
| Writes/day | 1,000 | Unlimited | |
| Key size | 512 bytes | 512 bytes | |
| Value size | 25 MB | 25 MB |
6.5 Optimization Strategies
✅ Good: Caching responses
export default {async fetch(request: Request, env: Env): Promise<Response> {const cache = caches.default;const cacheKey = new Request(request.url, request);// Try cache firstlet response = await cache.match(cacheKey);if (!response) {// Cache miss - fetch from originresponse = await fetch(request);// Cache for 1 hourresponse = new Response(response.body, response);response.headers.set('Cache-Control', 'public, max-age=3600');await cache.put(cacheKey, response.clone());}return response;},};
✅ Good: Lazy loading and code splitting
// Use dynamic imports to reduce initial bundle sizeexport default {async fetch(request: Request, env: Env): Promise<Response> {const url = new URL(request.url);if (url.pathname.startsWith('/admin')) {// Only load admin module when neededconst { handleAdmin } = await import('./admin');return handleAdmin(request, env);}return new Response('Home page');},};
7. Wrangler CLI
Wrangler is the official CLI tool for managing Cloudflare Workers.
7.1 Project Initialization
✅ Good: Create new Worker project
# Create new project with interactive promptswrangler init my-worker# Create with TypeScriptwrangler init my-worker --type typescript# Create from templatewrangler init my-worker --from-dash
✅ Good: Project structure
my-worker/├── src/│ └── index.ts├── wrangler.toml├── package.json└── tsconfig.json
7.2 Configuration (wrangler.toml)
✅ Good: Complete wrangler.toml example
name = "my-worker"main = "src/index.ts"compatibility_date = "2024-11-01"compatibility_flags = ["nodejs_compat"]# Workers Paid planworkers_dev = trueaccount_id = "your-account-id"# Routesroutes = [{ pattern = "example.com/api/*", zone_name = "example.com" }]# Environment variables[vars]ENVIRONMENT = "production"LOG_LEVEL = "info"# KV namespaces[[kv_namespaces]]binding = "MY_KV"id = "abc123"# Durable Objects[[durable_objects.bindings]]name = "COUNTER"class_name = "Counter"script_name = "my-worker"# R2 buckets[[r2_buckets]]binding = "MY_BUCKET"bucket_name = "my-bucket"# Service bindings[[services]]binding = "AUTH_SERVICE"service = "auth-worker"# Build configuration[build]command = "npm run build"[build.upload]format = "modules"main = "./dist/index.js"# Environments[env.staging]vars = { ENVIRONMENT = "staging" }routes = [{ pattern = "staging.example.com/*", zone_name = "example.com" }][env.production]vars = { ENVIRONMENT = "production" }routes = [{ pattern = "example.com/*", zone_name = "example.com" }]
7.3 Local Development
✅ Good: Local dev workflows
# Start local dev serverwrangler dev# Dev with remote resources (KV, Durable Objects)wrangler dev --remote# Dev with specific portwrangler dev --port 3000# Dev with live reloadwrangler dev --live-reload# Dev with specific environmentwrangler dev --env staging# Test locallycurl http://localhost:8787/api/test
7.4 Deployment
✅ Good: Deployment commands
# Deploy to productionwrangler deploy# Deploy to specific environmentwrangler deploy --env staging# Dry run (validate without deploying)wrangler deploy --dry-run# Deploy with verbose outputwrangler deploy --verbose# Rollback to previous versionwrangler rollback
7.5 Secrets Management
✅ Good: Managing secrets securely
# Add secretwrangler secret put API_KEY# Prompt: Enter a secret value: ********# Add secret for specific environmentwrangler secret put API_KEY --env production# List secretswrangler secret list# Delete secretwrangler secret delete API_KEY# Bulk secret managementecho "SECRET_VALUE" | wrangler secret put SECRET_KEY
7.6 Tailing Logs
✅ Good: Real-time log monitoring
# Tail logs in real-timewrangler tail# Tail with filteringwrangler tail --status error# Tail specific environmentwrangler tail --env production# Tail with JSON outputwrangler tail --format json# Sample logswrangler tail --sampling-rate 0.1 # 10% of requests
// Logging in Workerexport default {async fetch(request: Request, env: Env): Promise<Response> {console.log('Request received:', request.url);console.error('Error occurred:', new Error('Sample error'));console.warn('Warning: High memory usage');return new Response('OK');},};
✅ Good: KV management via CLI
# Create KV namespacewrangler kv:namespace create "MY_KV"# List namespaceswrangler kv:namespace list# Put key-valuewrangler kv:key put --binding=MY_KV "key" "value"# Get valuewrangler kv:key get --binding=MY_KV "key"# Delete keywrangler kv:key delete --binding=MY_KV "key"# List keyswrangler kv:key list --binding=MY_KV --prefix="user:"# Bulk upload from JSONwrangler kv:bulk put --binding=MY_KV data.json
8. Use Cases & Patterns
8.1 API Proxying
✅ Good: Proxy with caching
export default {async fetch(request: Request, env: Env): Promise<Response> {const url = new URL(request.url);// Proxy to backend APIconst backendUrl = `https://api.backend.com${url.pathname}`;// Check cache firstconst cacheKey = new Request(backendUrl);const cachedResponse = await caches.default.match(cacheKey);if (cachedResponse) {return cachedResponse;}// Fetch from backendconst response = await fetch(backendUrl, {headers: {'Authorization': `Bearer ${env.API_KEY}`,},});// Cache successful responsesif (response.ok) {const responseToCache = response.clone();await caches.default.put(cacheKey, responseToCache);}return response;},};
8.2 A/B Testing
✅ Good: Edge-based A/B testing
export default {async fetch(request: Request, env: Env): Promise<Response> {const url = new URL(request.url);// Determine variant based on cookie or random assignmentconst variant = getVariant(request);if (variant === 'A') {return fetch(`https://variant-a.example.com${url.pathname}`);} else {return fetch(`https://variant-b.example.com${url.pathname}`);}},};function getVariant(request: Request): 'A' | 'B' {const cookie = request.headers.get('Cookie');if (cookie?.includes('variant=A')) {return 'A';}if (cookie?.includes('variant=B')) {return 'B';}// Random assignment (50/50)return Math.random() < 0.5 ? 'A' : 'B';}
8.3 Geo-Routing
✅ Good: Route based on location
export default {async fetch(request: Request, env: Env): Promise<Response> {const country = request.cf?.country as string;// Route to regional backendif (country === 'US' || country === 'CA') {return fetch('https://us-backend.example.com', request);} else if (country === 'GB' || country === 'DE' || country === 'FR') {return fetch('https://eu-backend.example.com', request);} else {return fetch('https://global-backend.example.com', request);}},};
8.4 Authentication at the Edge
✅ Good: JWT validation at the edge
export default {async fetch(request: Request, env: Env): Promise<Response> {const authHeader = request.headers.get('Authorization');if (!authHeader?.startsWith('Bearer ')) {return new Response('Unauthorized', { status: 401 });}const token = authHeader.slice(7);// Validate JWT (simplified)const isValid = await validateJWT(token, env.JWT_SECRET);if (!isValid) {return new Response('Invalid token', { status: 403 });}// Forward to backendreturn fetch('https://backend.example.com', request);},};async function validateJWT(token: string, secret: string): Promise<boolean> {// JWT validation logic (use library like jose)return true; // Simplified}
❌ Bad: Insecure authentication
// ❌ Never expose secrets in responsesexport default {async fetch(request: Request, env: Env): Promise<Response> {return Response.json({ secret: env.API_KEY }); // ❌ Exposed!},};
Summary
This comprehensive Cloudflare Workers skill covers:
- Worker Basics: Fetch handlers, routing, local dev, deployment
- KV Storage: Key-value operations, TTL, caching patterns, limits
- Durable Objects: Stateful computing, WebSockets, coordination, alarms
- R2 Storage: Object storage, uploads, downloads, multipart uploads
- Bindings: KV, Durable Objects, R2, service bindings, secrets
- Performance & Limits: CPU, memory, request limits, optimization
- Wrangler CLI: Init, config, dev, deploy, secrets, logs
- Use Cases: API proxying, A/B testing, geo-routing, edge auth
Use Cloudflare Workers for edge computing, low-latency APIs, global state management, and serverless applications at scale.