Agents cloud-cloudflare-workers-dev
Cloudflare Workers development guide for building serverless edge applications. Use this skill when creating Workers, configuring wrangler.toml, using D1/KV/R2 storage, implementing Durable Objects, deploying to Cloudflare, or building edge functions with TypeScript.
install
source · Clone the upstream repo
git clone https://github.com/aRustyDev/agents
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/aRustyDev/agents "$T" && mkdir -p ~/.claude/skills && cp -r "$T/content/skills/cloud-cloudflare-workers-dev" ~/.claude/skills/arustydev-agents-cloud-cloudflare-workers-dev && rm -rf "$T"
manifest:
content/skills/cloud-cloudflare-workers-dev/SKILL.mdsource content
Cloudflare Workers Development
Overview
Build serverless applications on Cloudflare's edge platform with millisecond cold starts using V8 isolates.
Prerequisites
# Install Wrangler CLI npm install -g wrangler # Authenticate wrangler login # Create new project npm create cloudflare@latest my-worker
Project Structure
my-worker/ ├── src/ │ └── index.ts # Entry point ├── wrangler.toml # Configuration ├── package.json └── tsconfig.json
Basic Worker
export default { async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> { const url = new URL(request.url); if (url.pathname === '/api/hello') { return Response.json({ message: 'Hello from the edge!' }); } return new Response('Not Found', { status: 404 }); }, } satisfies ExportedHandler<Env>;
Configuration (wrangler.toml)
name = "my-worker" main = "src/index.ts" compatibility_date = "2024-01-01" # Environment variables [vars] API_VERSION = "v1" # D1 Database [[d1_databases]] binding = "DB" database_name = "my-database" database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" # KV Namespace [[kv_namespaces]] binding = "KV" id = "xxxxxxxx" # R2 Bucket [[r2_buckets]] binding = "BUCKET" bucket_name = "my-bucket" # Durable Objects [[durable_objects.bindings]] name = "COUNTER" class_name = "Counter" [[migrations]] tag = "v1" new_classes = ["Counter"] # Queues [[queues.producers]] queue = "my-queue" binding = "QUEUE" [[queues.consumers]] queue = "my-queue" max_batch_size = 10 # Scheduled (Cron) [triggers] crons = ["0 * * * *"] # Every hour
Storage Patterns
D1 (SQLite Database)
interface Env { DB: D1Database; } // Query const { results } = await env.DB.prepare( 'SELECT * FROM users WHERE id = ?' ).bind(userId).all(); // Insert await env.DB.prepare( 'INSERT INTO users (name, email) VALUES (?, ?)' ).bind(name, email).run(); // Batch operations await env.DB.batch([ env.DB.prepare('INSERT INTO logs (msg) VALUES (?)').bind('log1'), env.DB.prepare('INSERT INTO logs (msg) VALUES (?)').bind('log2'), ]);
KV (Key-Value Store)
interface Env { KV: KVNamespace; } // Get with type const value = await env.KV.get<User>('user:123', 'json'); // Put with TTL (seconds) await env.KV.put('session:abc', JSON.stringify(data), { expirationTtl: 3600, }); // List keys with prefix const { keys } = await env.KV.list({ prefix: 'user:' }); // Delete await env.KV.delete('user:123');
R2 (Object Storage)
interface Env { BUCKET: R2Bucket; } // Upload await env.BUCKET.put('files/document.pdf', fileBody, { httpMetadata: { contentType: 'application/pdf' }, customMetadata: { uploadedBy: userId }, }); // Download const object = await env.BUCKET.get('files/document.pdf'); if (object) { return new Response(object.body, { headers: { 'Content-Type': object.httpMetadata?.contentType || '' }, }); } // Stream large files const object = await env.BUCKET.get('large-file.zip'); return new Response(object?.body, { headers: { 'Content-Disposition': 'attachment; filename="large-file.zip"' }, }); // Delete await env.BUCKET.delete('files/document.pdf');
Durable Objects
// Durable Object class export class Counter implements DurableObject { private value: number = 0; constructor(private state: DurableObjectState, private env: Env) { this.state.blockConcurrencyWhile(async () => { this.value = (await this.state.storage.get<number>('value')) || 0; }); } async fetch(request: Request): Promise<Response> { const url = new URL(request.url); if (url.pathname === '/increment') { this.value++; await this.state.storage.put('value', this.value); } return Response.json({ value: this.value }); } } // Using from Worker export default { async fetch(request: Request, env: Env): Promise<Response> { const id = env.COUNTER.idFromName('global'); const stub = env.COUNTER.get(id); return stub.fetch(request); }, };
Durable Objects with WebSocket
export class ChatRoom implements DurableObject { private sessions: Map<WebSocket, { name: string }> = new Map(); async fetch(request: Request): Promise<Response> { if (request.headers.get('Upgrade') !== 'websocket') { return new Response('Expected WebSocket', { status: 400 }); } const pair = new WebSocketPair(); const [client, server] = Object.values(pair); this.state.acceptWebSocket(server); this.sessions.set(server, { name: 'anonymous' }); return new Response(null, { status: 101, webSocket: client }); } async webSocketMessage(ws: WebSocket, message: string) { const data = JSON.parse(message); // Broadcast to all connected clients for (const [socket] of this.sessions) { socket.send(JSON.stringify(data)); } } async webSocketClose(ws: WebSocket) { this.sessions.delete(ws); } }
Queues
interface Env { QUEUE: Queue; } // Producer await env.QUEUE.send({ type: 'email', to: 'user@example.com', subject: 'Welcome!', }); // Send batch await env.QUEUE.sendBatch([ { body: { type: 'task1' } }, { body: { type: 'task2' } }, ]); // Consumer export default { async queue(batch: MessageBatch<QueueMessage>, env: Env): Promise<void> { for (const message of batch.messages) { try { await processMessage(message.body); message.ack(); } catch (error) { message.retry(); } } }, };
Scheduled Workers (Cron)
export default { async scheduled(event: ScheduledEvent, env: Env, ctx: ExecutionContext) { ctx.waitUntil(doCleanup(env)); }, }; // wrangler.toml // [triggers] // crons = ["0 0 * * *"] # Daily at midnight
Authentication Patterns
JWT Verification
import { jwtVerify } from 'jose'; async function verifyToken(token: string, env: Env): Promise<JWTPayload | null> { try { const secret = new TextEncoder().encode(env.JWT_SECRET); const { payload } = await jwtVerify(token, secret); return payload; } catch { return null; } } // Middleware pattern async function authMiddleware(request: Request, env: Env): Promise<Response | null> { const auth = request.headers.get('Authorization'); if (!auth?.startsWith('Bearer ')) { return Response.json({ error: 'Unauthorized' }, { status: 401 }); } const payload = await verifyToken(auth.slice(7), env); if (!payload) { return Response.json({ error: 'Invalid token' }, { status: 401 }); } return null; // Continue to handler }
Session with KV
async function createSession(userId: string, env: Env): Promise<string> { const sessionId = crypto.randomUUID(); await env.KV.put(`session:${sessionId}`, userId, { expirationTtl: 86400, // 24 hours }); return sessionId; } async function getSession(sessionId: string, env: Env): Promise<string | null> { return env.KV.get(`session:${sessionId}`); }
Rate Limiting
async function rateLimit( key: string, limit: number, window: number, env: Env ): Promise<boolean> { const current = await env.KV.get<number>(`ratelimit:${key}`, 'json') || 0; if (current >= limit) { return false; } await env.KV.put(`ratelimit:${key}`, JSON.stringify(current + 1), { expirationTtl: window, }); return true; } // Usage const allowed = await rateLimit(clientIP, 100, 60, env); if (!allowed) { return Response.json({ error: 'Rate limited' }, { status: 429 }); }
API Routing with Hono
import { Hono } from 'hono'; import { cors } from 'hono/cors'; const app = new Hono<{ Bindings: Env }>(); app.use('*', cors()); app.get('/api/users', async (c) => { const { results } = await c.env.DB.prepare('SELECT * FROM users').all(); return c.json(results); }); app.post('/api/users', async (c) => { const { name, email } = await c.req.json(); await c.env.DB.prepare('INSERT INTO users (name, email) VALUES (?, ?)') .bind(name, email) .run(); return c.json({ success: true }, 201); }); app.get('/api/users/:id', async (c) => { const { results } = await c.env.DB.prepare('SELECT * FROM users WHERE id = ?') .bind(c.req.param('id')) .all(); return results[0] ? c.json(results[0]) : c.notFound(); }); export default app;
Caching Strategies
// Cache-first with stale-while-revalidate async function cachedFetch( request: Request, env: Env, ctx: ExecutionContext ): Promise<Response> { const cache = caches.default; const cacheKey = new Request(request.url, request); // Check cache let response = await cache.match(cacheKey); if (response) { // Revalidate in background ctx.waitUntil( fetch(request).then((fresh) => { cache.put(cacheKey, fresh.clone()); }) ); return response; } // Fetch and cache response = await fetch(request); ctx.waitUntil(cache.put(cacheKey, response.clone())); return response; }
Development Workflow
# Local development wrangler dev # Local with bindings wrangler dev --local --persist # Deploy to production wrangler deploy # Deploy to staging wrangler deploy --env staging # Tail logs wrangler tail # Create D1 database wrangler d1 create my-database # Run D1 migrations wrangler d1 migrations apply my-database # Create KV namespace wrangler kv:namespace create MY_KV # Create R2 bucket wrangler r2 bucket create my-bucket
Testing
import { unstable_dev } from 'wrangler'; import { describe, expect, it, beforeAll, afterAll } from 'vitest'; describe('Worker', () => { let worker: UnstableDevWorker; beforeAll(async () => { worker = await unstable_dev('src/index.ts', { experimental: { disableExperimentalWarning: true }, }); }); afterAll(async () => { await worker.stop(); }); it('responds with hello', async () => { const resp = await worker.fetch('/api/hello'); const data = await resp.json(); expect(data.message).toBe('Hello from the edge!'); }); });
Environment Types
interface Env { // Variables API_VERSION: string; // Secrets (set via wrangler secret put) JWT_SECRET: string; API_KEY: string; // Bindings DB: D1Database; KV: KVNamespace; BUCKET: R2Bucket; COUNTER: DurableObjectNamespace; QUEUE: Queue; }
See Also
- Reference: wrangler-config.md
- Reference: d1-patterns.md
- Reference: deployment.md