Overview
Cloudflare Workers run JavaScript (or WASM) at the edge, globally, with zero cold starts. A Worker handles HTTP requests in a fetch handler, optionally reading from KV for low-latency key-value lookups. The broader platform reference lives in cloudflare-workers; KV specifics are in cloudflare-kv.
Prerequisites
- A Cloudflare account. Free tier supports Workers with 100,000 daily requests.
- Node 20+ and npm on the path.
- Wrangler installed globally:
npm install -g wrangler. - Authenticated:
wrangler loginopens the browser OAuth flow.
Steps
1. Initialize the project
npm create cloudflare@latest my-worker
cd my-workerChoose “Hello World” when prompted for a template. This generates wrangler.toml, src/index.ts, and a minimal tsconfig.json.
Key fields in wrangler.toml:
name = "my-worker"
main = "src/index.ts"
compatibility_date = "2025-01-01"Set compatibility_date to a recent date to opt into the latest runtime behaviour flags. Do not leave it at the scaffold default from years ago.
2. Write the fetch handler
Every Worker exports a fetch function that receives a Request and returns a Response.
// src/index.ts
export interface Env {
MY_KV: KVNamespace;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
if (url.pathname === "/health") {
return new Response("ok", { status: 200 });
}
const key = url.searchParams.get("key");
if (!key) {
return new Response("Missing key param", { status: 400 });
}
const value = await env.MY_KV.get(key);
if (!value) {
return new Response("Not found", { status: 404 });
}
return Response.json({ key, value });
},
};Type the Env interface to match the bindings declared in wrangler.toml. The TypeScript compiler catches missing bindings before deployment.
3. Bind KV storage
Create a KV namespace in the Cloudflare dashboard or via wrangler:
wrangler kv namespace create MY_KV
# Outputs: id = "abc123..."
wrangler kv namespace create MY_KV --preview
# Outputs: preview_id = "def456..."Add the binding to wrangler.toml:
[[kv_namespaces]]
binding = "MY_KV"
id = "abc123..."
preview_id = "def456..."The binding value matches the key in the Env interface. The preview_id is used during local development. See cloudflare-kv for TTL and metadata patterns.
4. Test locally
wrangler dev
# Worker running at http://localhost:8787In another terminal:
# Write a value to the preview namespace
wrangler kv key put --namespace-id=def456... "hello" "world"
# Test the endpoint
curl "http://localhost:8787/?key=hello"
# {"key":"hello","value":"world"}wrangler dev hot-reloads on file changes. The local runtime matches the production runtime closely; most bugs surface here before deployment.
5. Deploy to production
wrangler deploy
# Uploading my-worker...
# Published my-worker (1.23 sec)
# https://my-worker.your-subdomain.workers.devThe worker is live globally within seconds. Verify with curl:
curl "https://my-worker.your-subdomain.workers.dev/health"
# okTo add a custom domain, configure a route in wrangler.toml:
routes = [
{ pattern = "api.example.com/*", zone_name = "example.com" }
]See cloudflare for DNS setup and vercel-vs-cloudflare for when Workers is the better choice over Vercel Edge Functions.
6. Wire to CI for automated deploys
Add a GitHub Actions step to deploy on push to main. Store CLOUDFLARE_API_TOKEN in GitHub secrets. See github-actions for the full workflow pattern.
- name: Deploy Worker
run: wrangler deploy --env production
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}Verify it worked
# Health check
curl -si "https://my-worker.your-subdomain.workers.dev/health" | head -5
# HTTP/2 200
# KV round-trip
wrangler kv key put --namespace-id=abc123... "ping" "pong"
curl "https://my-worker.your-subdomain.workers.dev/?key=ping"
# {"key":"ping","value":"pong"}
# Tail live logs
wrangler tail my-workerCommon errors
Error: No account id found. Addaccount_id = "..."towrangler.tomlor setCLOUDFLARE_ACCOUNT_IDin the environment.- KV binding returns
undefined. Thebindingname inwrangler.tomldoes not match the field name inEnv. Both must be identical. compatibility_datewarning. The date inwrangler.tomlis in the past; update it to within the last 12 months to use current runtime flags.- Deploy fails with “script too large”. Workers have a 1 MB compressed script limit. Use dynamic imports or move heavy logic to a Durable Object; see cloudflare-durable-objects.
- Local
wrangler devdiffers from production. Some APIs (likecrypto.subtle) behave differently in the local runtime. Test withwrangler dev --remoteto run against the real edge infrastructure.