Skip to main content

Command Palette

Search for a command to run...

Cloudflare Workers Reference Sheet

Updated
5 min read
Cloudflare Workers Reference Sheet
T

Just a guy who loves to write code and watch anime.

Core Concepts

Workers Runtime: V8 isolates (not Node.js) running on Cloudflare's edge network

  • Cold start: <1ms vs 100ms+ for containers

  • Web APIs only (no fs, process.env)

  • Each request is isolated and stateless

Basic Structure:

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    // Your logic here
    return new Response('Hello World');
  }
}

Request Handling

Request Object:

const url = new URL(request.url);
const method = request.method; // GET, POST, etc.
const headers = request.headers;
const body = await request.json(); // or .text()

// Query parameters
const userId = url.searchParams.get('userId'); // ?userId=123

Response Object:

// Simple text
return new Response('Hello');

// JSON with headers
return new Response(JSON.stringify({data: 'value'}), {
  status: 201,
  headers: { 'Content-Type': 'application/json' }
});

// Error response
return new Response(JSON.stringify({error: 'Bad request'}), {
  status: 400,
  headers: { 'Content-Type': 'application/json' }
});

Basic Routing

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    const url = new URL(request.url);
    const path = url.pathname;
    const method = request.method;

    if (path === '/api/users' && method === 'GET') {
      return new Response(JSON.stringify([{id: 1, name: 'Alice'}]), {
        headers: { 'Content-Type': 'application/json' }
      });
    }

    if (path === '/api/users' && method === 'POST') {
      const userData = await request.json();
      // Validation
      if (!userData.name) {
        return new Response(JSON.stringify({error: 'Name required'}), {
          status: 400,
          headers: { 'Content-Type': 'application/json' }
        });
      }
      // Process...
      return new Response(JSON.stringify({id: 123, ...userData}), {
        status: 201,
        headers: { 'Content-Type': 'application/json' }
      });
    }

    return new Response('Not Found', { status: 404 });
  }
}

Environment & Configuration

wrangler.toml:

name = "my-worker"
compatibility_date = "2024-01-01"

# Non-secret environment variables
[vars]
API_URL = "https://api.example.com"
DEBUG = "true"

Secrets (sensitive data):

# Set secrets via CLI
wrangler secret put API_KEY
wrangler secret put DATABASE_PASSWORD

Using in code:

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    const apiKey = env.API_KEY; // secret
    const apiUrl = env.API_URL; // regular env var

    return new Response(`Using ${apiUrl}`);
  }
}

TypeScript setup:

# Generate types for your specific Worker config
pnpm wrangler types

Add to tsconfig.json:

{
  "compilerOptions": {
    "types": ["./worker-configuration.d.ts"]
  }
}

External API Calls

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    try {
      // Call external API
      const response = await fetch('https://api.example.com/data', {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${env.API_TOKEN}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({ query: 'example' })
      });

      if (!response.ok) {
        return new Response(JSON.stringify({error: 'External API failed'}), {
          status: 500,
          headers: { 'Content-Type': 'application/json' }
        });
      }

      const data = await response.json();

      // Transform and return
      return new Response(JSON.stringify({
        result: data.someField,
        timestamp: Date.now()
      }), {
        headers: { 'Content-Type': 'application/json' }
      });

    } catch (error) {
      return new Response(JSON.stringify({error: 'Request failed'}), {
        status: 500,
        headers: { 'Content-Type': 'application/json' }
      });
    }
  }
}

Caching

Cache API (like a persistent Map<string, Response>):

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    const cache = caches.default; // or await caches.open('my-cache')
    const cacheKey = 'weather-london';

    // Try cache first
    const cachedResponse = await cache.match(cacheKey);
    if (cachedResponse) {
      return cachedResponse;
    }

    // Cache miss - fetch fresh data
    const freshResponse = await fetch(`https://api.weather.com?key=${env.API_KEY}`);
    const data = await freshResponse.json();

    const response = new Response(JSON.stringify(data), {
      headers: { 
        'Content-Type': 'application/json',
        'Cache-Control': 'max-age=300' // Browser caches for 5 min
      }
    });

    // Store in Cloudflare cache (background)
    ctx.waitUntil(cache.put(cacheKey, response.clone()));

    return response;
  }
}

Key concepts:

  • caches.default = global cache, caches.open('name') = named cache

  • response.clone() = needed because Response can only be read once

  • ctx.waitUntil() = keep Worker alive for background task, don't block response

  • Cache-Control header = browser caching, separate from caches API

Middleware Pattern

// Middleware functions return Response to stop, null to continue
async function corsMiddleware(request: Request): Promise<Response | null> {
  if (request.method === 'OPTIONS') {
    return new Response(null, {
      headers: {
        'Access-Control-Allow-Origin': '*',
        'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE',
        'Access-Control-Allow-Headers': 'Content-Type, Authorization'
      }
    });
  }
  return null;
}

async function authMiddleware(request: Request): Promise<Response | null> {
  const authHeader = request.headers.get('Authorization');
  if (!authHeader?.startsWith('Bearer ')) {
    return new Response(JSON.stringify({error: 'Unauthorized'}), {
      status: 401,
      headers: { 'Content-Type': 'application/json' }
    });
  }
  return null;
}

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    // Run middleware chain
    const middlewares = [corsMiddleware];
    for (const middleware of middlewares) {
      const response = await middleware(request);
      if (response) return response;
    }

    // Protected routes
    const url = new URL(request.url);
    if (url.pathname.startsWith('/api/protected/')) {
      const authResponse = await authMiddleware(request);
      if (authResponse) return authResponse;
    }

    // Your routes...
    return new Response('OK');
  }
}

Streaming Responses

For large datasets or real-time data:

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    const stream = new ReadableStream({
      async start(controller) {
        for (let i = 0; i < 10000; i++) {
          // Each line is a separate JSON object
          const chunk = JSON.stringify({id: i, data: `Item ${i}`}) + '\n';
          controller.enqueue(new TextEncoder().encode(chunk));

          // Yield occasionally
          if (i % 100 === 0) {
            await new Promise(resolve => setTimeout(resolve, 0));
          }
        }
        controller.close();
      }
    });

    return new Response(stream, {
      headers: {
        'Content-Type': 'application/x-ndjson', // Newline-delimited JSON
        'Transfer-Encoding': 'chunked'
      }
    });
  }
}

Production Patterns

Error handling & logging:

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    const startTime = Date.now();

    try {
      // Your logic...
      const response = new Response(JSON.stringify({success: true}), {
        headers: { 'Content-Type': 'application/json' }
      });

      // Success metrics
      console.log(`Success: ${request.method} ${new URL(request.url).pathname} - ${Date.now() - startTime}ms`);
      return response;

    } catch (error) {
      console.error(`Error: ${request.method} ${request.url}`, error);
      return new Response(JSON.stringify({error: 'Internal Server Error'}), {
        status: 500,
        headers: { 'Content-Type': 'application/json' }
      });
    }
  }
}

Simple rate limiting:

const rateLimits = new Map<string, {count: number; resetTime: number}>();

function isRateLimited(ip: string, limit: number, windowMs: number): boolean {
  const now = Date.now();
  const current = rateLimits.get(ip);

  // No data or window expired - reset
  if (!current || now > current.resetTime) {
    rateLimits.set(ip, {count: 1, resetTime: now + windowMs});
    return false;
  }

  // Hit limit
  if (current.count >= limit) {
    return true;
  }

  // Increment and allow
  current.count++;
  return false;
}

Key Reminders

  • Stateless: Variables don't persist between requests

  • Web APIs: Use fetch, Request, Response, URL - not Node.js APIs

  • Secrets vs Env Vars: Secrets via CLI, regular config in wrangler.toml

  • TypeScript: Run wrangler types after config changes

  • Caching: caches API for persistence, ctx.waitUntil() for background tasks

  • Rate Limiting: Implement your own, resets on each deployment (use KV for persistence)

Deployment

# Deploy to production
wrangler deploy

# Deploy to staging
wrangler deploy --env staging

# View logs
wrangler tail