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.

install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
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"
manifest: skills/data/cloudflare-workers-pascallammers-mylo-travel-concierg/SKILL.md
source content

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 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

LimitFreePaid
Reads/day100,000Unlimited
Writes/day1,000Unlimited
Deletes/day1,000Unlimited
Lists/day1,000Unlimited
Key size512 bytes512 bytes
Value size25 MB25 MB
Metadata size1024 bytes1024 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

PlanCPU Time (HTTP)CPU Time (Cron)
Free10 ms10 ms
Paid30 seconds15 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

LimitValue
Request URL16 KB
Request headers128 KB
Request body (Free/Pro)100 MB
Request body (Business)200 MB
Request body (Enterprise)500 MB
Response headers128 KB
Subrequests50 (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)

LimitFreePaid
Reads/day100,000Unlimited
Writes/day1,000Unlimited
Key size512 bytes512 bytes
Value size25 MB25 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:

  1. Worker Basics: Fetch handlers, routing, local dev, deployment
  2. KV Storage: Key-value operations, TTL, caching patterns, limits
  3. Durable Objects: Stateful computing, WebSockets, coordination, alarms
  4. R2 Storage: Object storage, uploads, downloads, multipart uploads
  5. Bindings: KV, Durable Objects, R2, service bindings, secrets
  6. Performance & Limits: CPU, memory, request limits, optimization
  7. Wrangler CLI: Init, config, dev, deploy, secrets, logs
  8. 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.