tech/cloudflare/workers

WORKERS

Cloudflare Workers runtime.

production Cloudflare Workers (V8 isolates, Node.js compat mode)
requires: tech/cloudflare
improves: tech/cloudflare

Cloudflare Workers

Cloudflare Workers run JavaScript/TypeScript in V8 isolates at Cloudflare's edge — zero cold starts, global distribution, bound to your account's storage and AI services.

CPU limit: 10ms (free) / 30s (paid Unbound). Memory: 128MB per isolate. Requests: 100k/day (free) / 10M+/month (Workers Paid, $5/mo).

wrangler.toml (canonical pattern)

name = "my-worker"
main = "src/index.ts"
compatibility_date = "2024-12-01"
compatibility_flags = ["nodejs_compat"]   # enables Node.js built-ins

[vars]
ENV = "production"

[[d1_databases]]
binding = "DB"
database_name = "my-db"
database_id = "YOUR_D1_ID"

[[kv_namespaces]]
binding = "KV"
id = "YOUR_KV_ID"

[[r2_buckets]]
binding = "R2"
bucket_name = "my-bucket"

[ai]
binding = "AI"

[[queues.producers]]
binding = "QUEUE"
queue = "my-queue"

Worker entrypoint

export interface Env {
  DB: D1Database;
  KV: KVNamespace;
  R2: R2Bucket;
  AI: Ai;
  QUEUE: Queue;
  // Vars (from [vars] or wrangler secret)
  ENV: string;
  API_KEY: string;
}

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

    // ctx.waitUntil — runs after response is sent (logging, cache warming)
    ctx.waitUntil(logRequest(env, request));

    if (url.pathname === '/api/data') {
      const rows = await env.DB.prepare('SELECT * FROM items LIMIT 50').all();
      return Response.json(rows.results);
    }

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

  // Queue consumer (if this Worker is a queue consumer)
  async queue(batch: MessageBatch<unknown>, env: Env): Promise<void> {
    for (const msg of batch.messages) {
      await processMessage(msg.body, env);
      msg.ack();
    }
  },
};

Streaming responses (SSE / AI)

// Stream Workers AI output to client
const stream = await env.AI.run('@cf/meta/llama-3.1-8b-instruct', {
  messages: [{ role: 'user', content: prompt }],
  stream: true,
});

return new Response(stream, {
  headers: {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    'Connection': 'keep-alive',
  },
});

Service bindings (Worker-to-Worker)

# In wrangler.toml — call another Worker directly (no HTTP overhead)
[[services]]
binding = "AUTH_SERVICE"
service = "auth-worker"
// In code
const response = await env.AUTH_SERVICE.fetch(request.clone());

Secrets

wrangler secret put API_KEY          # prompts for value, encrypts at rest
wrangler secret list                 # list secret names (not values)
wrangler secret delete API_KEY

Secrets are available as env.API_KEY in the Worker. Never put secrets in [vars] — those are plaintext in wrangler.toml.

Key commands

wrangler dev                          # local dev (port 8787)
wrangler dev --remote                 # dev against production bindings
wrangler deploy                       # deploy to production
wrangler tail                         # stream live logs
wrangler deployments list             # deployment history
wrangler rollback <version-id>        # roll back to a previous version

waitUntil() pattern (post-response work)

ctx.waitUntil((async () => {
  // Runs after response is sent — use for logging, analytics, cache warming
  await env.KV.put(`log:${Date.now()}`, JSON.stringify({ path: url.pathname }), { expirationTtl: 86400 });
})());

Common Gotchas

Free vs Paid

LimitFreeWorkers Paid ($5/mo)
Requests100k/day10M/month (+$0.30/M)
CPU time10ms/request30s/request (Unbound)
Durable Objects
Log tailing

See Also