workers
π―Skillfrom null-shot/cloudflare-skills
Generates microservices and handles HTTP, scheduled, and queue events using TypeScript and Cloudflare Workers configuration.
Part of
null-shot/cloudflare-skills(11 items)
Installation
npx wrangler typesnpx wrangler secret put API_KEYSkill Details
Core Workers fundamentals including handlers, configuration, and Service Bindings. Load when creating new Workers, configuring wrangler.jsonc, implementing fetch/scheduled/queue handlers, using Service Bindings for RPC, generating types with wrangler types, or building microservices.
Overview
# Cloudflare Workers
Essential patterns for building Cloudflare Workers applications with TypeScript, proper configuration, and Service Bindings for microservices.
FIRST: Project Setup
Initialize a new Workers project:
```bash
npm create cloudflare@latest my-worker
# OR
wrangler init my-worker
```
Minimal wrangler.jsonc:
```jsonc
{
"name": "my-worker",
"main": "src/index.ts",
"compatibility_date": "2025-03-07",
"compatibility_flags": ["nodejs_compat"],
"observability": {
"enabled": true,
"head_sampling_rate": 1
}
}
```
Code Standards
| Standard | Requirement | Notes |
|----------|-------------|-------|
| Language | TypeScript by default | JavaScript only if explicitly requested |
| Module Format | ES modules only | NEVER use Service Worker format |
| Imports | Always import types/classes | Must import all used methods |
| File Structure | Single file unless specified | Keep code in one file by default |
| Dependencies | Minimize external deps | Use official SDKs when available |
| Native Bindings | Not supported | Avoid FFI/C bindings |
| Types | Include TypeScript types | Define Env interface for bindings |
Handler Patterns
HTTP Request Handler (fetch)
```typescript
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise
const url = new URL(request.url);
// Route handling
if (url.pathname === "/api/data") {
return handleAPI(request, env);
}
return new Response("Hello World!", {
headers: { "Content-Type": "text/plain" }
});
}
};
async function handleAPI(request: Request, env: Env): Promise
// Validate request method
if (request.method !== "POST") {
return new Response("Method not allowed", { status: 405 });
}
try {
const data = await request.json();
// Process data...
return Response.json({ success: true, data });
} catch (error) {
return Response.json(
{ error: "Invalid JSON" },
{ status: 400 }
);
}
}
```
Scheduled Handler (cron)
```typescript
export default {
async scheduled(event: ScheduledEvent, env: Env, ctx: ExecutionContext): Promise
// Run scheduled tasks
console.log("Cron triggered:", new Date(event.scheduledTime).toISOString());
// Use waitUntil for background work
ctx.waitUntil(performCleanup(env));
}
};
async function performCleanup(env: Env): Promise
// Background cleanup logic
console.log("Cleanup completed");
}
```
wrangler.jsonc configuration:
```jsonc
{
"triggers": {
"crons": ["0 /6 "] // Every 6 hours
}
}
```
Queue Consumer Handler
```typescript
export default {
async queue(batch: MessageBatch
for (const message of batch.messages) {
try {
await processMessage(message.body, env);
message.ack();
} catch (error) {
console.error("Message processing failed:", error);
message.retry();
}
}
}
};
type QueueMessage = {
id: string;
data: unknown;
};
async function processMessage(body: QueueMessage, env: Env): Promise
// Process queue message
console.log("Processing message:", body.id);
}
```
Auto-Generate Environment Types
RECOMMENDED: Use wrangler types to automatically generate your Env interface from your wrangler.jsonc:
```bash
# Generate types from wrangler.jsonc
npx wrangler types
# Output to custom path
npx wrangler types ./types/env.d.ts
# Include runtime types (Wrangler >= 3.66.0)
npx wrangler types --experimental-include-runtime
```
This generates a worker-configuration.d.ts file with:
- Env interface matching all your bindings (KV, R2, D1, secrets, etc.)
- Runtime types matching your
compatibility_dateandcompatibility_flags - Service binding types with full RPC method signatures
Add to tsconfig.json:
```jsonc
{
"compilerOptions": {
"types": ["@cloudflare/workers-types", "./worker-configuration"]
}
}
```
When to regenerate:
- After adding/removing bindings in wrangler.jsonc
- After changing compatibility_date or compatibility_flags
- After modifying .dev.vars (secrets)
- Before deploying (run in CI/CD)
Example generated Env interface:
```typescript
// worker-configuration.d.ts (auto-generated)
interface Env {
// From wrangler.jsonc bindings
MY_KV: KVNamespace;
MY_BUCKET: R2Bucket;
DB: D1Database;
COUNTER: DurableObjectNamespace;
AUTH_SERVICE: Service
AI: Ai;
MY_QUEUE: Queue;
// From .dev.vars (secrets)
DATABASE_URL: string;
API_KEY: string;
// From wrangler.jsonc vars
ENVIRONMENT: "development" | "staging" | "production";
API_VERSION: string;
}
```
Secrets Management
CRITICAL: Never put secrets in wrangler.jsonc! Secrets must be encrypted and hidden.
Secrets vs Environment Variables
| Type | Storage | Use For | Visibility |
|------|---------|---------|------------|
| vars (wrangler.jsonc) | Plaintext | Non-sensitive config (URLs, flags) | β Visible |
| secrets | Encrypted | API keys, passwords, tokens | β Hidden |
Local Development with .dev.vars
Create a .dev.vars file for local secrets (NEVER commit this file):
```bash
# .dev.vars (add to .gitignore)
DATABASE_URL="postgresql://localhost:5432/dev"
API_KEY="dev-key-12345"
STRIPE_SECRET="sk_test_..."
```
CI/CD Best Practice: Empty .dev.vars
For CI/CD and type generation, commit a .dev.vars with empty values:
```bash
# .dev.vars (committed to git)
# Real values set via: wrangler secret put
DATABASE_URL=""
API_KEY=""
STRIPE_SECRET=""
```
Why this works:
wrangler typesreads.dev.varsto generateEnvtypes- Empty values create correct TypeScript types
- CI/CD can run type checking without real secrets
- Production secrets are set via
wrangler secret putor dashboard
Setting Production Secrets
Via Wrangler:
```bash
# Add/update secret (deploys immediately)
npx wrangler secret put API_KEY
# You'll be prompted for value
# List secrets (values never shown)
npx wrangler secret list
# Delete secret
npx wrangler secret delete API_KEY
```
Via Dashboard:
Workers & Pages β Your Worker β Settings β Variables and Secrets β Add β Secret
Accessing secrets in code:
```typescript
interface Env {
DATABASE_URL: string;
API_KEY: string;
}
export default {
async fetch(request: Request, env: Env): Promise
// Access secrets from env (same as regular env vars)
const db = new Database(env.DATABASE_URL);
// Validate API key
const key = request.headers.get("x-api-key");
if (key !== env.API_KEY) {
return new Response("Unauthorized", { status: 401 });
}
return Response.json({ success: true });
}
};
```
Secret Store (Account-Level Secrets)
For secrets shared across multiple Workers:
```jsonc
{
"secrets_store_secrets": [
{
"binding": "SHARED_API_KEY",
"store_id": "abc123def456",
"secret_name": "GLOBAL_API_KEY"
}
]
}
```
Accessing Secret Store:
```typescript
interface Env {
SHARED_API_KEY: {
get(): Promise
};
}
export default {
async fetch(request: Request, env: Env): Promise
// Secret Store requires .get() call
const apiKey = await env.SHARED_API_KEY.get();
return Response.json({ success: true });
}
};
```
See [references/secrets.md](references/secrets.md) for complete secrets management guide.
wrangler.jsonc Configuration
Complete example with common bindings:
```jsonc
{
"name": "my-worker",
"main": "src/index.ts",
"compatibility_date": "2025-03-07",
"compatibility_flags": ["nodejs_compat"],
"observability": {
"enabled": true,
"head_sampling_rate": 1
},
"vars": {
"ENVIRONMENT": "production"
},
"kv_namespaces": [
{ "binding": "MY_KV", "id": "your-kv-id" }
],
"r2_buckets": [
{ "binding": "MY_BUCKET", "bucket_name": "my-bucket" }
],
"d1_databases": [
{ "binding": "DB", "database_name": "my-db", "database_id": "your-db-id" }
],
"durable_objects": {
"bindings": [
{ "name": "COUNTER", "class_name": "Counter" }
]
},
"queues": {
"producers": [
{ "binding": "MY_QUEUE", "queue": "my-queue" }
]
}
}
```
Key Configuration Rules:
- Use
wrangler.jsonc, NOTwrangler.toml - Set
compatibility_dateto current date (format:YYYY-MM-DD) - Always include
compatibility_flags: ["nodejs_compat"] - Enable observability with
head_sampling_rate: 1for full logging - Only include bindings that are actually used in your code
- Never include npm dependencies in wrangler.jsonc
See [references/configuration.md](references/configuration.md) for complete configuration options.
Background Tasks with waitUntil
Offload non-critical work to run after the response is sent:
```typescript
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise
// Return fast response
const response = Response.json({ status: "accepted" });
// Process in background (doesn't block response)
ctx.waitUntil(performAsyncWork(request, env));
return response;
}
};
async function performAsyncWork(request: Request, env: Env): Promise
// This runs after the response is sent
const data = await request.json();
await env.MY_KV.put("processed", JSON.stringify(data));
}
```
Use waitUntil for:
- Analytics tracking
- Cache warming
- Logging
- Non-critical database writes
- Cleanup operations
Error Handling
HTTP Status Codes
```typescript
async function handleRequest(request: Request, env: Env): Promise
try {
// 400 - Bad Request
if (!request.headers.get("content-type")) {
return Response.json({ error: "Content-Type required" }, { status: 400 });
}
// 401 - Unauthorized
const apiKey = request.headers.get("x-api-key");
if (apiKey !== env.API_KEY) {
return Response.json({ error: "Invalid API key" }, { status: 401 });
}
// 404 - Not Found
const resource = await env.MY_KV.get("resource");
if (!resource) {
return Response.json({ error: "Resource not found" }, { status: 404 });
}
// 200 - Success
return Response.json({ success: true, data: resource });
} catch (error) {
// 500 - Internal Server Error
console.error("Request failed:", error);
return Response.json(
{ error: "Internal server error" },
{ status: 500 }
);
}
}
```
Error Boundaries
```typescript
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise
try {
return await handleRequest(request, env, ctx);
} catch (error) {
console.error("Unhandled error:", error);
return Response.json(
{
error: "An unexpected error occurred",
message: error instanceof Error ? error.message : "Unknown error"
},
{ status: 500 }
);
}
}
};
```
Service Bindings (Microservices)
Service Bindings are the recommended way to build multi-Worker architectures. They enable Worker-to-Worker communication with zero latency, no HTTP overhead, and no additional costs.
Why Service Bindings?
| Benefit | Description |
|---------|-------------|
| Zero latency | Both Workers run on same thread/server by default |
| No HTTP overhead | Direct RPC calls, not HTTP requests |
| Zero additional cost | Split functionality without increasing bills |
| Type-safe RPC | Call methods with full TypeScript support |
| Internal-only Workers | Build services not exposed to public internet |
| Independent deployment | Each Worker deploys on its own schedule |
RPC Interface (Recommended)
Export an RPC class from your service Worker:
```typescript
// auth-service/src/index.ts
import { WorkerEntrypoint } from "cloudflare:workers";
export class AuthService extends WorkerEntrypoint
async validateToken(token: string): Promise<{ valid: boolean; userId?: string }> {
const userId = await this.env.AUTH_TOKENS.get(token);
return { valid: !!userId, userId: userId || undefined };
}
async createToken(userId: string): Promise
const token = crypto.randomUUID();
await this.env.AUTH_TOKENS.put(token, userId, { expirationTtl: 86400 });
return token;
}
}
// Must also export default handler for HTTP access (if needed)
export default {
async fetch(request: Request, env: Env): Promise
return new Response("Auth Service - use RPC interface");
}
} satisfies ExportedHandler
```
auth-service/wrangler.jsonc:
```jsonc
{
"name": "auth-service",
"main": "src/index.ts",
"compatibility_date": "2025-03-07",
"kv_namespaces": [
{ "binding": "AUTH_TOKENS", "id": "..." }
]
}
```
Calling the service from another Worker:
```typescript
// api-worker/src/index.ts
interface Env {
AUTH: Service
}
export default {
async fetch(request: Request, env: Env): Promise
const token = request.headers.get("Authorization")?.slice(7);
if (!token) {
return new Response("Unauthorized", { status: 401 });
}
// Call RPC method directly
const result = await env.AUTH.validateToken(token);
if (!result.valid) {
return new Response("Invalid token", { status: 401 });
}
return Response.json({ userId: result.userId });
}
};
```
api-worker/wrangler.jsonc:
```jsonc
{
"name": "api-worker",
"main": "src/index.ts",
"compatibility_date": "2025-03-07",
"services": [
{
"binding": "AUTH",
"service": "auth-service"
}
]
}
```
Generate types for Service Bindings:
```bash
# In api-worker directory
npx wrangler types
```
This auto-generates the Env interface with the correct Service type.
Fetch-Based Service Binding
For simpler use cases or when you don't need RPC:
```typescript
// api-worker/src/index.ts
interface Env {
AUTH: Fetcher;
}
export default {
async fetch(request: Request, env: Env): Promise
// Forward request to auth service
const authResponse = await env.AUTH.fetch(new Request("https://internal/validate", {
method: "POST",
body: JSON.stringify({ token: "..." })
}));
const result = await authResponse.json();
return Response.json(result);
}
};
```
See [references/service-bindings.md](references/service-bindings.md) for advanced patterns including environment-specific bindings and testing.
Detailed References
- [references/handlers.md](references/handlers.md) - Complete handler API reference, context objects, advanced routing
- [references/configuration.md](references/configuration.md) - Full wrangler.jsonc options, environment-specific config
- [references/service-bindings.md](references/service-bindings.md) - Advanced Service Bindings patterns, testing, environment routing
- [references/secrets.md](references/secrets.md) - Complete secrets management guide, .dev.vars, Secret Store, CI/CD best practices
Best Practices
- Use TypeScript by default - Better type safety and IDE support
- Generate types with wrangler types - Auto-generate Env interface from config and .dev.vars
- NEVER put secrets in wrangler.jsonc - Use
wrangler secret putor .dev.vars for local dev - Use .dev.vars with empty values for CI - Enables type generation without exposing secrets
- Enable observability - Set
observability.enabled: truefor logging - Use Service Bindings for microservices - Zero-cost, type-safe Worker-to-Worker calls
- Validate all inputs - Never trust user data
- Handle errors gracefully - Use try-catch and return appropriate status codes
- Use waitUntil for background tasks - Don't block response on non-critical work
- Keep bundle size small - Minimize dependencies for faster cold starts
Common Patterns
Complete API Worker
```typescript
interface Env {
MY_KV: KVNamespace;
API_KEY: string;
}
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise
try {
// CORS preflight
if (request.method === "OPTIONS") {
return new Response(null, {
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type",
}
});
}
// Route handling
const url = new URL(request.url);
if (url.pathname === "/api/data" && request.method === "GET") {
const data = await env.MY_KV.get("data");
return Response.json({ data: data || null });
}
if (url.pathname === "/api/data" && request.method === "POST") {
const body = await request.json();
await env.MY_KV.put("data", JSON.stringify(body));
return Response.json({ success: true });
}
return new Response("Not found", { status: 404 });
} catch (error) {
console.error("Request failed:", error);
return Response.json(
{ error: "Internal server error" },
{ status: 500 }
);
}
}
};
```
Middleware Pattern
```typescript
type Middleware = (
request: Request,
env: Env,
ctx: ExecutionContext,
next: () => Promise
) => Promise
const authMiddleware: Middleware = async (request, env, ctx, next) => {
const apiKey = request.headers.get("x-api-key");
if (apiKey !== env.API_KEY) {
return new Response("Unauthorized", { status: 401 });
}
return next();
};
const loggingMiddleware: Middleware = async (request, env, ctx, next) => {
console.log(${request.method} ${request.url});
const response = await next();
console.log(Response: ${response.status});
return response;
};
function compose(...middlewares: Middleware[]) {
return async (request: Request, env: Env, ctx: ExecutionContext): Promise
let index = 0;
const next = async (): Promise
if (index >= middlewares.length) {
return handleRequest(request, env);
}
const middleware = middlewares[index++];
return middleware(request, env, ctx, next);
};
return next();
};
}
export default {
fetch: compose(loggingMiddleware, authMiddleware)
};
```
Resources
- [Cloudflare Workers Documentation](https://developers.cloudflare.com/workers)
- [Workers Examples](https://developers.cloudflare.com/workers/examples)
- [Wrangler CLI Documentation](https://developers.cloudflare.com/workers/wrangler)
More from this repository10
Enables reliable asynchronous message processing on Cloudflare Workers, supporting background tasks, batch operations, retry logic, and decoupled message handling.
Builds AI agents on Cloudflare with persistent state, real-time WebSockets, scheduled tasks, and tool integration.
Enables high-performance, scalable event tracking and analytics by writing and querying event data with SQL across various use cases like user metrics, billing, and telemetry.
Builds and deploys secure, scalable Model Context Protocol servers on Cloudflare Workers with OAuth authentication and custom tools.
Skill
Manages Cloudflare Workers development and deployment with comprehensive CLI commands for resources like Workers, KV, R2, and more.
Stores and retrieves globally distributed key-value data for fast edge caching, session management, configuration, and user preferences.
Enables serverless SQLite database operations at the edge, supporting structured data, SQL queries, migrations, and complex relational data management in Cloudflare Workers.
Enables building stateful, persistent AI agents with advanced features like RPC, scheduling, state management, and streaming chat on Cloudflare Workers.
Skill