Cloudflare Workers Serverless Edge Computing Guide: Deploy Global APIs in 2026
Cloudflare Workers Serverless Edge Computing Guide: Deploy Global APIs in 2026
Cloudflare Workers is a serverless platform that runs JavaScript, TypeScript, Rust, Python, and WebAssembly code in 330+ edge locations worldwide. Unlike traditional serverless platforms (AWS Lambda, Google Cloud Functions) that run in a single region, Workers run at the edge — closest to the user. This means a user in Tokyo hits a Worker in Tokyo, and a user in London hits a Worker in London. The result: sub-50ms response times globally, zero cold starts, and automatic scaling to millions of requests. The free plan includes 100,000 requests per day and 10ms CPU time per invocation. This guide covers the complete setup from installation to production deployment with real code examples.
Why Cloudflare Workers in 2026
The serverless edge computing market has grown rapidly. Here is how Workers compares.
| Platform | Edge Locations | Free Plan | Cold Starts | Languages | Max Execution Time |
|---|---|---|---|---|---|
| Cloudflare Workers | 330+ | 100K req/day | None | JS, TS, Rust, Python, WASM | 30s (50ms CPU) |
| AWS Lambda@Edge | 600+ (CloudFront PoPs) | No free tier | Yes (50-500ms) | Node.js, Python | 30s |
| AWS Lambda | 30+ regions | 1M req/month free | Yes (50-500ms) | Node.js, Python, Java, Go | 15 min |
| Deno Deploy | 35+ regions | 1M req/month | None | TS, JS | 5 min (50ms CPU) |
| Vercel Edge Functions | 100+ regions | 100K req/month | None | JS, TS | 30s |
| Fastly Compute@Edge | 80+ PoPs | No free tier | None | Rust, JS, WASM | 60s (50ms CPU) |
| Netlify Edge Functions | 100+ regions | 125K req/month | None | JS, TS | 30s |
Workers wins on global reach at zero cost. 330+ edge locations, no cold starts, and 100,000 free requests per day. The main trade-off is the 10ms CPU time limit on the free plan (50ms on paid), which means Workers are best for I/O-heavy work (APIs, routing, auth, caching) not CPU-heavy work (image processing, ML).
Cloudflare Workers Pricing in 2026
| Plan | Monthly Cost | Requests | CPU Time | Key Features |
|---|---|---|---|---|
| Free | $0 | 100K/day | 10ms | KV (100K reads/day), 100K writes/day |
| Paid (Bundled) | $5 | 10M/month included, $0.30/M after | 50ms | D1, R2, Queues, Cron Triggers |
| Paid (Unbound) | $0 | Pay per use | 30s | $0.50/M requests, $12.50/M GB-sec |
| Workers Standard | $5 + usage | Unlimited | 30s | Custom limits, priority support |
Detailed Pricing for Storage Add-ons
| Service | Free Tier | Paid Pricing | Best For |
|---|---|---|---|
| Workers KV | 100K reads, 1K writes/day | $0.50/M reads, $5/M writes, $0.50/GB storage | Key-value cache, config |
| Workers D1 | 5GB storage, 5M rows read/day | $0.75/M rows read, $19.08/M rows written | SQLite at edge |
| Workers R2 | 10GB storage, 1M Class A ops/month | $0.015/GB/month, $4.50/M Class A ops | S3-compatible object storage |
| Durable Objects | 100K requests/day | $0.15/M requests + $12.50/M GB-sec | Stateful, real-time, WebSocket |
| Workers Queues | 100K operations/day | $0.40/M operations | Async message queues |
| Workers Analytics | 100K events/day | $0.60/M events | Custom analytics at edge |
| Workers Email | 200 emails/day | $0.40/1K emails | Send email from Workers |
For a typical API serving 1M requests/month with KV storage and D1 database, your cost would be approximately:
- Workers requests: $5 (bundled plan, 10M included)
- KV reads (1M): $0.50
- D1 reads (5M): $3.75
- D1 storage (1GB): ~$0.02
- Total: ~$9.27/month
Compare to AWS Lambda + API Gateway + DynamoDB for the same load: approximately $25-40/month.
Step 1: Installing Wrangler CLI
Wrangler is the CLI tool for building, testing, and deploying Workers.
1.1 Install Node.js and Wrangler
# Install Node.js 18+ (if not installed)
# macOS
brew install node@20
# Ubuntu/Debian
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs
# Install Wrangler CLI globally
npm install -g wrangler
# Verify installation
wrangler --version
# wrangler 3.90.0 or later
1.2 Authenticate with Cloudflare
# Login to Cloudflare (opens browser)
wrangler login
# Or use an API token
export CLOUDFLARE_API_TOKEN=your_token_here
# Verify authentication
wrangler whoami
1.3 Create Your First Worker
# Scaffold a new Worker project
npm create cloudflare@latest my-first-worker
# Choose:
# - "Hello World" starter
# - TypeScript
# - Git: Yes
cd my-first-worker
npm install
This creates:
my-first-worker/
├── src/
│ └── index.ts
├── wrangler.toml
├── package.json
├── tsconfig.json
└── .gitignore
1.4 The Worker Code
Open src/index.ts:
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
const url = new URL(request.url);
if (url.pathname === "/") {
return new Response(JSON.stringify({
message: "Hello from Cloudflare Workers!",
timestamp: Date.now(),
location: request.cf?.colo || "unknown",
}), {
headers: { "Content-Type": "application/json" },
});
}
if (url.pathname === "/health") {
return new Response("OK", { status: 200 });
}
return new Response("Not Found", { status: 404 });
},
};
The request.cf object contains Cloudflare-specific data: the colo (data center code), country, city, timezone, and more. This lets you serve location-aware responses.
1.5 Test Locally
# Start local dev server
wrangler dev
# Output:
# wrangler dev uses localhost:8787
# Ready on http://localhost:8787
# Test in another terminal
curl http://localhost:8787/
# {"message":"Hello from Cloudflare Workers!","timestamp":...,"location":"LHR"}
curl http://localhost:8787/health
# OK
1.6 Deploy to the Edge
# Deploy globally to 330+ edge locations
wrangler deploy
# Output:
# Uploaded my-first-worker (1.23 sec)
# Published my-first-worker (0.45 sec)
# https://my-first-worker.username.workers.dev
# Deployed to my-first-worker triggers (0.32 sec)
Your Worker is now live globally. Test it:
curl https://my-first-worker.username.workers.dev/
# {"message":"Hello from Cloudflare Workers!","timestamp":...,"location":"SIN"}
The location field changes based on which edge location serves the request.
Step 2: Workers KV — Key-Value Storage
Workers KV is a globally distributed key-value store. Data written in one location is available in all 330+ locations within seconds.
2.1 Create a KV Namespace
# Create a KV namespace
wrangler kv:namespace create "MY_KV"
# Output:
# { "id": "abc123...", "title": "MY_KV" }
Add the namespace to wrangler.toml:
[[kv_namespaces]]
binding = "MY_KV"
id = "abc123..."
2.2 Read and Write in a Worker
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
// Write a value
if (url.pathname === "/api/set" && request.method === "POST") {
const body = await request.json();
await env.MY_KV.put("user:123", JSON.stringify(body));
return new Response("Saved", { status: 200 });
}
// Read a value
if (url.pathname === "/api/get") {
const value = await env.MY_KV.get("user:123");
if (!value) {
return new Response("Not found", { status: 404 });
}
return new Response(value, {
headers: { "Content-Type": "application/json" },
});
}
return new Response("Not Found", { status: 404 });
},
};
2.3 KV Performance and Limits
| Metric | Free Plan | Paid Plan |
|---|---|---|
| Reads | 100,000/day | 10M/month included, $0.50/M after |
| Writes | 1,000/day | 1M/month included, $5/M after |
| Storage | 1GB | $0.50/GB/month |
| Key size | 512 bytes | 512 bytes |
| Value size | 25MB | 25MB |
| Read latency | ~10ms | ~10ms |
| Write propagation | 60 seconds | 60 seconds |
KV is eventually consistent — writes propagate globally within 60 seconds. For strong consistency, use Durable Objects.
Step 3: Workers D1 — SQLite at the Edge
D1 is Cloudflare's serverless SQLite database that runs at the edge.
3.1 Create a D1 Database
# Create a D1 database
wrangler d1 create my-database
# Output:
# [[d1_databases]]
# binding = "DB"
# database_name = "my-database"
# database_id = "abc123-..."
Add to wrangler.toml:
[[d1_databases]]
binding = "DB"
database_name = "my-database"
database_id = "abc123-..."
3.2 Run Migrations
Create migrations/0001_init.sql:
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
email TEXT UNIQUE NOT NULL,
name TEXT NOT NULL,
created_at TEXT DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS posts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
title TEXT NOT NULL,
content TEXT,
published INTEGER DEFAULT 0,
created_at TEXT DEFAULT (datetime('now')),
FOREIGN KEY (user_id) REFERENCES users(id)
);
CREATE INDEX idx_posts_user ON posts(user_id);
CREATE INDEX idx_posts_published ON posts(published);
Apply the migration:
wrangler d1 migrations apply my-database --local # local dev
wrangler d1 migrations apply my-database --remote # production
3.3 Query D1 in a Worker
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
// Create a user
if (url.pathname === "/api/users" && request.method === "POST") {
const { email, name } = await request.json();
const stmt = env.DB.prepare(
"INSERT INTO users (email, name) VALUES (?, ?) RETURNING *"
).bind(email, name);
const result = await stmt.first();
return Response.json(result);
}
// List published posts
if (url.pathname === "/api/posts" && request.method === "GET") {
const { results } = await env.DB.prepare(
"SELECT * FROM posts WHERE published = 1 ORDER BY created_at DESC LIMIT 20"
).all();
return Response.json(results);
}
// Get a single post
if (url.pathname.startsWith("/api/posts/") && request.method === "GET") {
const id = url.pathname.split("/")[3];
const post = await env.DB.prepare(
"SELECT * FROM posts WHERE id = ?"
).bind(id).first();
if (!post) return new Response("Not found", { status: 404 });
return Response.json(post);
}
return new Response("Not Found", { status: 404 });
},
};
3.4 D1 vs Other Serverless Databases
| Database | Free Tier | Pricing | Latency | Best For |
|---|---|---|---|---|
| Cloudflare D1 | 5GB, 5M reads/day | $0.75/M reads, $19.08/M writes | ~5ms edge | SQLite workloads at edge |
| PlanetScale | Removed free tier | $39/mo (1GB) | 50-200ms (region) | MySQL, branching |
| Supabase | 500MB, 2 projects | $25/mo (8GB) | 50-200ms (region) | Postgres, real-time |
| Neon | 3GB, 100 compute hr/mo | $19/mo (10GB) | 50-200ms (region) | Serverless Postgres |
| Turso | 9GB, 1B row reads/mo | $29/mo (1GB+) | ~5ms edge | SQLite at edge |
D1 wins for SQLite workloads at the edge. For Postgres, consider Supabase or Neon.
Step 4: Workers R2 — S3-Compatible Object Storage
R2 is Cloudflare's object storage that is S3-compatible and has zero egress fees — you pay for storage and operations, not bandwidth.
4.1 Create an R2 Bucket
wrangler r2 bucket create my-images
Add to wrangler.toml:
[[r2_buckets]]
binding = "MY_IMAGES"
bucket_name = "my-images"
4.2 Upload and Serve Files
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
// Upload a file
if (url.pathname === "/api/upload" && request.method === "POST") {
const formData = await request.formData();
const file = formData.get("file") as File;
const key = `uploads/${Date.now()}-${file.name}`;
await env.MY_IMAGES.put(key, file.stream(), {
httpMetadata: { contentType: file.type },
});
return Response.json({ url: `https://my-images.username.r2.dev/${key}` });
}
// Serve a file
if (url.pathname.startsWith("/files/")) {
const key = url.pathname.replace("/files/", "");
const object = await env.MY_IMAGES.get(key);
if (!object) return new Response("Not found", { status: 404 });
return new Response(object.body, {
headers: { "Content-Type": object.httpMetadata?.contentType || "application/octet-stream" },
});
}
return new Response("Not Found", { status: 404 });
},
};
4.3 R2 Pricing Comparison
| Provider | Storage | Egress | Operations | Free Tier |
|---|---|---|---|---|
| Cloudflare R2 | $0.015/GB/mo | $0 (free) | $4.50/M Class A, $0.36/M Class B | 10GB, 1M ops |
| AWS S3 Standard | $0.023/GB/mo | $0.09/GB | $5/M PUT, $0.40/M GET | 5GB (12 months) |
| Google Cloud Storage | $0.020/GB/mo | $0.12/GB | $5/M Class A, $0.40/M Class B | 5GB |
| Backblaze B2 | $0.006/GB/mo | Free up to 3x storage | $5/M Class A, free Class B | 10GB |
R2's zero egress is the killer feature. If you serve 1TB of images per month:
- AWS S3: $23 storage + $90 egress = $113/month
- Cloudflare R2: $15 storage + $0 egress = $15/month
Step 5: Durable Objects — Stateful Real-Time
Durable Objects provide strongly consistent state at the edge, with support for WebSockets. They are used for chat apps, collaborative editing, real-time games, and rate limiting.
5.1 Create a Durable Object
export class ChatRoom implements DurableObject {
state: DurableObjectState;
sessions: Map<WebSocket, string> = new Map();
constructor(state: DurableObjectState, env: Env) {
this.state = state;
}
async fetch(request: Request): Promise<Response> {
const pair = new WebSocketPair();
const [client, server] = Object.values(pair);
server.accept();
this.sessions.set(server, "");
server.addEventListener("message", (event) => {
for (const [ws] of this.sessions) {
ws.send(event.data);
}
});
server.addEventListener("close", () => {
this.sessions.delete(server);
});
return new Response(null, { status: 101, webSocket: client });
}
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
const roomId = url.pathname.split("/")[2] || "default";
const id = env.CHAT_ROOM.idFromName(roomId);
const stub = env.CHAT_ROOM.get(id);
return stub.fetch(request);
},
};
Add to wrangler.toml:
[[durable_objects.bindings]]
name = "CHAT_ROOM"
class_name = "ChatRoom"
[[migrations]]
tag = "v1"
new_classes = ["ChatRoom"]
Step 6: Custom Domains and Routing
6.1 Add a Custom Domain
- Go to Cloudflare Dashboard → Workers & Pages → Your Worker → Triggers → Custom Domains
- Click "Add Custom Domain"
- Enter your domain:
api.yoursite.com - Cloudflare automatically creates DNS records and issues an SSL certificate
- Wait 1-5 minutes for DNS to propagate
6.2 Route Multiple Workers on One Domain
Using Workers Routes (requires a domain on Cloudflare DNS):
- Go to your domain → Workers Routes → Add route
- Route:
api.yoursite.com/v1/*→ Worker:api-v1-worker - Route:
api.yoursite.com/v2/*→ Worker:api-v2-worker - Route:
yoursite.com/*→ Worker:main-site-worker
This lets you run multiple Workers on different paths of the same domain, like a reverse proxy.
Step 7: Cron Triggers
Workers can run on a schedule using Cron Triggers.
Add to wrangler.toml:
[triggers]
crons = ["0 * * * *", "*/15 * * * *"]
This triggers the Worker hourly and every 15 minutes.
export default {
async scheduled(event: ScheduledEvent, env: Env, ctx: ExecutionContext) {
// Check for expired tokens in KV
const keys = await env.MY_KV.list({ prefix: "token:" });
for (const key of keys.keys) {
const data = JSON.parse(await env.MY_KV.get(key.name) || "{}");
if (Date.now() > data.expiresAt) {
await env.MY_KV.delete(key.name);
}
}
},
async fetch(request: Request, env: Env): Promise<Response> {
return new Response("API running");
},
};
Step 8: Environment Variables and Secrets
8.1 Plain Environment Variables
[vars]
API_URL = "https://api.example.com"
MAX_REQUESTS = "100"
8.2 Secrets
# Set a secret (encrypted, not visible in dashboard)
wrangler secret put STRIPE_SECRET_KEY
# Enter value when prompted
wrangler secret put DATABASE_URL
Access in code:
const stripeKey = env.STRIPE_SECRET_KEY;
const dbUrl = env.DATABASE_URL;
Step 9: Performance Optimization
9.1 Use the Cache API
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const cacheKey = new Request(request.url, request);
const cache = caches.default;
// Check cache first
const cached = await cache.match(cacheKey);
if (cached) {
return cached;
}
// Generate response
const response = new Response(JSON.stringify({ time: Date.now() }), {
headers: {
"Content-Type": "application/json",
"Cache-Control": "s-maxage=3600",
},
});
// Store in cache
ctx.waitUntil(cache.put(cacheKey, response.clone()));
return response;
},
};
9.2 Performance Benchmarks
| Metric | Cloudflare Workers | AWS Lambda | Vercel Edge |
|---|---|---|---|
| Cold start | 0ms | 50-500ms | 0ms |
| Global p50 latency | 25ms | 100-300ms | 50-100ms |
| Global p99 latency | 50ms | 200-800ms | 100-200ms |
| Throughput (req/sec) | Unlimited (auto-scales) | 1,000-10,000/concurrent | Varies |
| Deploy time | 3-10 seconds | 30-60 seconds | 10-30 seconds |
Step 10: Monetizing Cloudflare Workers Skills
| Method | Effort | Income Potential | Time to First $ |
|---|---|---|---|
| Build and sell API services | High | $500-10,000/month | 2-6 months |
| Freelance Worker development | Medium | $50-150/hour | 1-4 weeks |
| Create and sell Worker templates | Medium | $200-2,000/month | 1-3 months |
| Tech blog tutorials | Medium | $200-1,000/article | 1-2 weeks |
| SaaS on Workers | Very high | $1,000-50,000/month | 6-24 months |
Build a Micro-SaaS on Workers
A practical side hustle: build a simple API service on Workers and charge for access.
- Idea: URL shortener API, image resizer, or IP geolocation API
- Stack: Workers + KV/D1 + Stripe for billing
- Pricing: Free tier (100 req/day) + Pro ($9/mo, 10K req/day) + Business ($49/mo, 100K req/day)
- Time to build: 2-4 weekends
- Cost: $5/month (Workers paid plan)
- Marketing: Post on Product Hunt, Hacker News, Reddit, Dev.to
Real example: An IP geolocation API on Workers using the request.cf data can be built in a weekend. With 50 paying customers at $9/month, that is $450/month in recurring revenue.
Action Checklist
- Create a Cloudflare account
- Install Node.js 20+ and Wrangler CLI
- Run
wrangler loginto authenticate - Create your first Worker with
npm create cloudflare@latest - Test locally with
wrangler dev - Deploy with
wrangler deploy - Create a KV namespace and use it in your Worker
- Create a D1 database and run migrations
- Create an R2 bucket and upload a file
- Add a custom domain to your Worker
- Set up environment variables and secrets
- Create a Cron Trigger for scheduled tasks
- Use the Cache API for response caching
- Monitor Workers in the Cloudflare dashboard
- Set up GitHub Actions for CI/CD
- Build and deploy a complete API service
Common Pitfalls and Solutions
| Pitfall | Impact | Solution |
|---|---|---|
| Exceeding 10ms CPU on free plan | Worker errors | Upgrade to paid ($5/mo) for 50ms |
| Treating KV as strongly consistent | Stale reads | Use Durable Objects for consistency |
| Not handling errors from D1 | API crashes | Use try/catch, return proper errors |
| Large responses over 25MB | KV truncation | Use R2 for large objects |
| No rate limiting | Abuse, cost overruns | Use Cloudflare WAF or Worker logic |
| No logging | Hard to debug | Use wrangler tail or Logpush |
| Testing only locally | Edge behavior differs | Test on *.workers.dev subdomain |
Final Word
Cloudflare Workers is the fastest and cheapest way to run code globally. For $0 (free plan) or $5/month (paid), you get 330+ edge locations, zero cold starts, sub-50ms latency worldwide, and a complete serverless ecosystem: KV for caching, D1 for SQLite at the edge, R2 for zero-egress object storage, Durable Objects for real-time state, and Cron Triggers for scheduled tasks. The setup takes 1-2 hours: install Wrangler, scaffold a Worker, test locally, and deploy. A typical API with 1M requests/month costs under $10 — a fraction of AWS or Vercel. For side hustles and small projects, Workers lets you build a global API, SaaS, or tool without managing a single server. Start with the free plan, build a simple API, add KV and D1 as you need them, and scale up only when traffic demands it.
More guides: bsynet.cc