Learn-skills.dev cloudflare-workers

Cloudflare Workers for edge computing, serverless functions, and global deployment. Use when user mentions "cloudflare workers", "wrangler", "edge functions", "serverless edge", "cloudflare pages", "D1 database", "R2 storage", "KV store", "workers AI", "edge computing", or deploying to Cloudflare.

install
source · Clone the upstream repo
git clone https://github.com/NeverSight/learn-skills.dev
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/NeverSight/learn-skills.dev "$T" && mkdir -p ~/.claude/skills && cp -r "$T/data/skills-md/1mangesh1/dev-skills-collection/cloudflare-workers" ~/.claude/skills/neversight-learn-skills-dev-cloudflare-workers && rm -rf "$T"
manifest: data/skills-md/1mangesh1/dev-skills-collection/cloudflare-workers/SKILL.md
source content

Cloudflare Workers

Setup

npm install -g wrangler && wrangler login
wrangler init my-worker

wrangler.toml

name = "my-worker"
main = "src/index.ts"
compatibility_date = "2024-01-01"
[vars]
ENVIRONMENT = "production"
[[kv_namespaces]]
binding = "MY_KV"
id = "abc123"
[[d1_databases]]
binding = "DB"
database_name = "my-db"
database_id = "def456"
[[r2_buckets]]
binding = "BUCKET"
bucket_name = "my-bucket"

Worker Basics

interface Env {
  MY_KV: KVNamespace; DB: D1Database; BUCKET: R2Bucket;
  ENVIRONMENT: string; API_KEY: string;
}

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    const url = new URL(request.url);
    if (url.pathname === "/api/health") return Response.json({ status: "ok" });
    if (request.method === "POST" && url.pathname === "/api/data") {
      const body = await request.json();
      return new Response("Created", { status: 201 });
    }
    return new Response("Not Found", { status: 404 });
  },
};

Wrangler CLI

wrangler dev                    # local dev server on localhost:8787
wrangler dev --remote           # dev against real Cloudflare infrastructure
wrangler deploy                 # deploy to production
wrangler tail                   # stream live logs from deployed worker
wrangler secret put API_KEY     # set an encrypted secret
wrangler secret list            # list configured secrets
wrangler delete                 # remove the deployed worker

Routing

Manual routing with a map, or use

hono
/
itty-router
for path params:

// Manual
const routes: Record<string, () => Promise<Response>> = {
  "/api/users": () => handleUsers(request),
  "/api/posts": () => handlePosts(request),
};
const handler = routes[new URL(request.url).pathname];
if (handler) return handler();

// Hono (recommended for complex routing)
import { Hono } from "hono";
const app = new Hono<{ Bindings: Env }>();
app.get("/users/:id", (c) => c.json({ id: c.req.param("id") }));
export default app;

KV Store

Global, low-latency key-value store. Eventually consistent. Best for read-heavy data.

wrangler kv namespace create MY_KV
wrangler kv namespace create MY_KV --preview
await env.MY_KV.put("user:123", JSON.stringify({ name: "Alice" }), {
  expirationTtl: 3600, metadata: { created: Date.now() },
});
const value = await env.MY_KV.get("user:123", "json");
const list = await env.MY_KV.list({ prefix: "user:", limit: 100 });
await env.MY_KV.delete("user:123");

D1 Database

SQLite at the edge with queries, transactions, and migrations.

wrangler d1 create my-db
wrangler d1 execute my-db --command "CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)"
wrangler d1 migrations create my-db init
wrangler d1 migrations apply my-db
const { results } = await env.DB.prepare("SELECT * FROM users WHERE id = ?").bind(userId).all();
await env.DB.prepare("INSERT INTO users (name) VALUES (?)").bind("Alice").run();
await env.DB.batch([
  env.DB.prepare("INSERT INTO users (name) VALUES (?)").bind("Bob"),
  env.DB.prepare("INSERT INTO users (name) VALUES (?)").bind("Carol"),
]);
const user = await env.DB.prepare("SELECT * FROM users WHERE id = ?").bind(1).first();

R2 Storage

S3-compatible object storage with no egress fees.

wrangler r2 bucket create my-bucket
// Upload
await env.BUCKET.put("images/photo.jpg", imageData, {
  httpMetadata: { contentType: "image/jpeg" },
});
// Download
const object = await env.BUCKET.get("images/photo.jpg");
if (object) {
  return new Response(object.body, {
    headers: { "Content-Type": object.httpMetadata?.contentType ?? "application/octet-stream" },
  });
}
// List, delete
const listed = await env.BUCKET.list({ prefix: "images/", limit: 50 });
await env.BUCKET.delete("images/photo.jpg");

For large files use

createMultipartUpload()
,
uploadPart()
,
complete()
.

Durable Objects

Strongly consistent, stateful compute. Each object has a unique ID and private storage. Use for rate limiters, WebSocket coordination, collaborative editing, session state.

[[durable_objects.bindings]]
name = "COUNTER"
class_name = "Counter"
[[migrations]]
tag = "v1"
new_classes = ["Counter"]
export class Counter implements DurableObject {
  constructor(private state: DurableObjectState, private env: Env) {}
  async fetch(request: Request): Promise<Response> {
    const current = (await this.state.storage.get<number>("count")) ?? 0;
    await this.state.storage.put("count", current + 1);
    return Response.json({ count: current + 1 });
  }
}
// Calling from a worker:
const id = env.COUNTER.idFromName("my-counter");
const response = await env.COUNTER.get(id).fetch(request);

Workers AI

Run ML models at the edge. Add

[ai]
with
binding = "AI"
to wrangler.toml.

// Text generation
const resp = await env.AI.run("@cf/meta/llama-3.1-8b-instruct", {
  messages: [{ role: "user", content: "Summarize this article." }],
});
// Embeddings
const emb = await env.AI.run("@cf/baai/bge-base-en-v1.5", { text: ["document to embed"] });
// Image classification
const cls = await env.AI.run("@cf/microsoft/resnet-50", { image: await request.arrayBuffer() });

Environment Variables and Secrets

Non-sensitive values go in

wrangler.toml
under
[vars]
. Set secrets with
wrangler secret put API_KEY
. Both accessed through
env.API_KEY
,
env.ENVIRONMENT
, etc.

Multiple environments:

[env.staging]
name = "my-worker-staging"
vars = { ENVIRONMENT = "staging" }
[env.production]
name = "my-worker-production"
vars = { ENVIRONMENT = "production" }

Deploy with

wrangler deploy --env staging
or
--env production
.

Cron Triggers

[triggers]
crons = ["0 */6 * * *", "30 8 * * 1"]
export default {
  async scheduled(event: ScheduledEvent, env: Env, ctx: ExecutionContext): Promise<void> {
    ctx.waitUntil(doCleanup(env));
  },
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    return new Response("OK");
  },
};

Test locally:

curl "http://localhost:8787/__scheduled?cron=0+*/6+*+*+*"

Middleware Patterns

CORS

function corsHeaders(origin: string): HeadersInit {
  return {
    "Access-Control-Allow-Origin": origin,
    "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
    "Access-Control-Allow-Headers": "Content-Type, Authorization",
  };
}

Auth

async function requireAuth(request: Request, env: Env): Promise<Response | null> {
  const token = request.headers.get("Authorization")?.replace("Bearer ", "");
  if (!token || token !== env.API_KEY) {
    return Response.json({ error: "Unauthorized" }, { status: 401 });
  }
  return null; // proceed
}

Rate Limiting

Use a Durable Object to track request timestamps per key. Store timestamps in storage, filter to the current window, reject if over limit, append and persist otherwise.

Local Development

wrangler dev                     # local server with miniflare runtime
wrangler dev --persist-to=./data # persist KV/D1/R2 data locally
wrangler dev --port 3000         # custom port
wrangler dev --remote            # proxy to Cloudflare (real bindings)

Miniflare simulates KV, D1, R2, Durable Objects, and caches locally.

Deployment

wrangler deploy                      # deploy to production
wrangler deploy --env staging        # named environment
wrangler deploy --dry-run            # validate without deploying
wrangler versions list               # list deployed versions
wrangler rollback                    # rollback to previous version

Custom domains:

routes = [{ pattern = "api.example.com/*", zone_name = "example.com" }]

Common Patterns

API Proxy

async function proxyRequest(request: Request, env: Env): Promise<Response> {
  const url = new URL(request.url);
  url.hostname = "api.upstream.com";
  return fetch(new Request(url.toString(), {
    method: request.method,
    headers: { ...Object.fromEntries(request.headers), "X-API-Key": env.UPSTREAM_KEY },
    body: request.body,
  }));
}

Edge Cache

async function cachedFetch(request: Request, ctx: ExecutionContext): Promise<Response> {
  const cache = caches.default;
  let response = await cache.match(request);
  if (response) return response;
  response = await fetch("https://api.origin.com" + new URL(request.url).pathname);
  const cached = new Response(response.body, response);
  cached.headers.set("Cache-Control", "s-maxage=300");
  ctx.waitUntil(cache.put(request, cached.clone()));
  return cached;
}

Webhook Handler

async function handleWebhook(request: Request, env: Env): Promise<Response> {
  const signature = request.headers.get("X-Signature-256") ?? "";
  const body = await request.text();
  const key = await crypto.subtle.importKey(
    "raw", new TextEncoder().encode(env.WEBHOOK_SECRET),
    { name: "HMAC", hash: "SHA-256" }, false, ["verify"]
  );
  const valid = await crypto.subtle.verify(
    "HMAC", key, hexToBytes(signature.replace("sha256=", "")),
    new TextEncoder().encode(body)
  );
  if (!valid) return new Response("Invalid signature", { status: 401 });
  return new Response("OK", { status: 200 });
}

URL Shortener

app.post("/shorten", async (c) => {
  const { url } = await c.req.json();
  const id = crypto.randomUUID().slice(0, 8);
  await c.env.MY_KV.put(`url:${id}`, url, { expirationTtl: 86400 * 30 });
  return c.json({ short: `${new URL(c.req.url).origin}/${id}` });
});
app.get("/:id", async (c) => {
  const target = await c.env.MY_KV.get(`url:${c.req.param("id")}`);
  if (!target) return c.text("Not found", 404);
  return c.redirect(target, 302);
});

Limits

ResourceFreePaid
CPU time/request10 ms30 s (Unbound) / 50 ms
Memory128 MB128 MB
Worker size1 MB10 MB
Subrequests (fetch)501000
KV reads/day100K10M+
KV writes/day1K1M+
D1 rows read/day5M50B
D1 rows written/day100K50M
R2 Class A ops/month1M$4.50/M
R2 storage10 GB$0.015/GB-mo
Cron triggers33+
Request body size100 MB100 MB

Key constraints: no raw TCP/UDP sockets (use WebSockets or Tunnels).

crypto.subtle
available. Node.js built-ins partially supported via
nodejs_compat
flag. Globals persist within an isolate but not across cold starts.
ctx.waitUntil()
extends execution after response for background work.