Claude-skill-registry cloudflare-workers
Auto-activates when user mentions Cloudflare Workers, edge functions, or serverless deployment. Expert in Cloudflare Workers including deployment, KV storage, and Durable Objects.
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/cloudflare-workers-pascallammers-mylo-travel-concierg" ~/.claude/skills/majiayu000-claude-skill-registry-cloudflare-workers && rm -rf "$T"
skills/data/cloudflare-workers-pascallammers-mylo-travel-concierg/SKILL.mdCloudflare 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 unpredictably export default { async fetch(request: Request): Promise<Response> { requestCount++; // ❌ Unreliable counter return new Response(`Count: ${requestCount}`); }, };
❌ Bad: Blocking synchronous operations
export default { async fetch(request: Request): Promise<Response> { // ❌ Don't use synchronous blocking operations const data = someHeavyComputationSync(); // Wastes CPU time return 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 KV return 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 globally npm install -g wrangler # Create new Worker project wrangler 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 port wrangler dev --port 3000 # Start with live reload wrangler dev --live-reload
✅ Good: Testing locally with curl
# Test GET request curl http://localhost:8787/ # Test POST with JSON curl -X POST http://localhost:8787/api/data \ -H "Content-Type: application/json" \ -d '{"key":"value"}' # Test with headers curl http://localhost:8787/api/users/123 \ -H "Authorization: Bearer token"
1.5 Deployment
✅ Good: Deploy to production
# Deploy Worker wrangler deploy # Deploy to specific environment wrangler deploy --env production # Deploy with verbose logging wrangler deploy --verbose # Tail logs after deployment wrangler 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 value const 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 expiration await env.MY_KV.put(key, value, { expirationTtl: ttl || 3600, // 1 hour default metadata: { 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 metadata const { 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 key await 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 purposes const 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 expiration await 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 keys do { 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 keys const 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 first let user = await env.CACHE.get(userId); if (!user) { // Cache miss - fetch from origin/database user = await fetchUserFromDatabase(userId); // Store in cache for 5 minutes await env.CACHE.put(userId, user, { expirationTtl: 300 }); } return new Response(user, { status: 200 }); }, }; async function fetchUserFromDatabase(userId: string): Promise<string> { // Simulate database fetch return 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 database await updateDatabase(userId!, newData); // Invalidate cache await 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 key const 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 writes for (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 storage this.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 execution const 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 triggers const taskId = await this.state.storage.get<string>('taskId'); console.log(`Executing task: ${taskId}`); // Perform scheduled work await 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 timestamps let timestamps = await this.state.storage.get<number[]>(key) || []; // Filter out old timestamps outside the window timestamps = timestamps.filter(ts => now - ts < window); if (timestamps.length >= limit) { return Response.json({ allowed: false, remaining: 0 }, { status: 429 }); } // Add current timestamp timestamps.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 ID const id = env.COUNTER.idFromName('global-counter'); const stub = env.COUNTER.get(id); // Forward request to Durable Object return 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 instance const 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 WebSocket const 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 connection this.state.acceptWebSocket(server); this.sessions.add(server); server.addEventListener('message', (event) => { // Broadcast message to all connected clients this.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 messages this.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 usage this.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 enabled this.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 WebSockets this.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 operation if (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 changes await 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 storage await 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 operations export class SimpleAPI { async fetch(request: Request): Promise<Response> { // ❌ No state needed, use regular Worker instead return Response.json({ message: 'Hello' }); } }
❌ Bad: Not persisting state to storage
export class BadCounter { private count: number = 0; // ❌ Only in memory, lost on restart async fetch(request: Request): Promise<Response> { this.count++; // ❌ Not persisted to storage return 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 R2 await 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 R2 const object = await env.MY_BUCKET.get(key); if (!object) { return new Response('Object not found', { status: 404 }); } // Return object with proper headers return 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 upload const 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 data const uploadedPart = await multipart.uploadPart(i + 1, chunk); parts.push(uploadedPart); } // Complete multipart upload const 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 request const largeFile = new Uint8Array(200 * 1024 * 1024); // 200 MB await 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 headers return 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 tokens export 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 hour const 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 control export 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.toml name = "worker-a" [[services]] binding = "WORKER_B" service = "worker-b"
// worker-a/index.ts interface 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.ts export 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 secret wrangler secret delete API_KEY # List secrets wrangler 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 code export 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 Object const result = await fetch('https://compute-service.example.com/heavy-task'); return result; }, };
❌ Bad: CPU-intensive operations
// ❌ Heavy computation exceeds CPU limits export 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 memory export 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 MB return 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 limit const urls = ['url1', 'url2', 'url3']; // Only 3 subrequests const 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 plan export 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 first let response = await cache.match(cacheKey); if (!response) { // Cache miss - fetch from origin response = await fetch(request); // Cache for 1 hour response = 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 size export 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 needed const { 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 prompts wrangler init my-worker # Create with TypeScript wrangler init my-worker --type typescript # Create from template wrangler 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 plan workers_dev = true account_id = "your-account-id" # Routes routes = [ { 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 server wrangler dev # Dev with remote resources (KV, Durable Objects) wrangler dev --remote # Dev with specific port wrangler dev --port 3000 # Dev with live reload wrangler dev --live-reload # Dev with specific environment wrangler dev --env staging # Test locally curl http://localhost:8787/api/test
7.4 Deployment
✅ Good: Deployment commands
# Deploy to production wrangler deploy # Deploy to specific environment wrangler deploy --env staging # Dry run (validate without deploying) wrangler deploy --dry-run # Deploy with verbose output wrangler deploy --verbose # Rollback to previous version wrangler rollback
7.5 Secrets Management
✅ Good: Managing secrets securely
# Add secret wrangler secret put API_KEY # Prompt: Enter a secret value: ******** # Add secret for specific environment wrangler secret put API_KEY --env production # List secrets wrangler secret list # Delete secret wrangler secret delete API_KEY # Bulk secret management echo "SECRET_VALUE" | wrangler secret put SECRET_KEY
7.6 Tailing Logs
✅ Good: Real-time log monitoring
# Tail logs in real-time wrangler tail # Tail with filtering wrangler tail --status error # Tail specific environment wrangler tail --env production # Tail with JSON output wrangler tail --format json # Sample logs wrangler tail --sampling-rate 0.1 # 10% of requests
// Logging in Worker export 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 namespace wrangler kv:namespace create "MY_KV" # List namespaces wrangler kv:namespace list # Put key-value wrangler kv:key put --binding=MY_KV "key" "value" # Get value wrangler kv:key get --binding=MY_KV "key" # Delete key wrangler kv:key delete --binding=MY_KV "key" # List keys wrangler kv:key list --binding=MY_KV --prefix="user:" # Bulk upload from JSON wrangler 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 API const backendUrl = `https://api.backend.com${url.pathname}`; // Check cache first const cacheKey = new Request(backendUrl); const cachedResponse = await caches.default.match(cacheKey); if (cachedResponse) { return cachedResponse; } // Fetch from backend const response = await fetch(backendUrl, { headers: { 'Authorization': `Bearer ${env.API_KEY}`, }, }); // Cache successful responses if (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 assignment const 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 backend if (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 backend return 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 responses export 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.