Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Comprehensive Cloudflare platform skill covering Workers, D1, R2, KV, AI, Durable Objects, and security.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/bindings/patterns.md
1# Binding Patterns and Best Practices23## Service Binding Patterns45### RPC via Service Bindings67```typescript8// auth-worker9export default {10async fetch(request: Request, env: Env) {11const token = request.headers.get('Authorization');12return new Response(JSON.stringify({ valid: await validateToken(token) }));13}14}1516// api-worker17const response = await env.AUTH_SERVICE.fetch(18new Request('https://fake-host/validate', {19headers: { 'Authorization': token }20})21);22```2324**Why RPC?** Zero latency (same datacenter), no DNS, free, type-safe.2526**HTTP vs Service:**27```typescript28// ❌ HTTP (slow, paid, cross-region latency)29await fetch('https://auth-worker.example.com/validate');3031// ✅ Service binding (fast, free, same isolate)32await env.AUTH_SERVICE.fetch(new Request('https://fake-host/validate'));33```3435**URL doesn't matter:** Service bindings ignore hostname/protocol, routing happens via binding name.3637### Typed Service RPC3839```typescript40// shared-types.ts41export interface AuthRequest { token: string; }42export interface AuthResponse { valid: boolean; userId?: string; }4344// auth-worker45export default {46async fetch(request: Request): Promise<Response> {47const body: AuthRequest = await request.json();48const response: AuthResponse = { valid: true, userId: '123' };49return Response.json(response);50}51}5253// api-worker54const response = await env.AUTH_SERVICE.fetch(55new Request('https://fake/validate', {56method: 'POST',57body: JSON.stringify({ token } satisfies AuthRequest)58})59);60const data: AuthResponse = await response.json();61```6263## Secrets Management6465```bash66# Set secret67npx wrangler secret put API_KEY68cat api-key.txt | npx wrangler secret put API_KEY69npx wrangler secret put API_KEY --env staging70```7172```typescript73// Use secret74const response = await fetch('https://api.example.com', {75headers: { 'Authorization': `Bearer ${env.API_KEY}` }76});77```7879**Never commit secrets:**80```jsonc81// ❌ NEVER82{ "vars": { "API_KEY": "sk_live_abc123" } }83```8485## Testing with Mock Bindings8687### Vitest Mock8889```typescript90import { vi } from 'vitest';9192const mockKV: KVNamespace = {93get: vi.fn(async (key) => key === 'test' ? 'value' : null),94put: vi.fn(async () => {}),95delete: vi.fn(async () => {}),96list: vi.fn(async () => ({ keys: [], list_complete: true, cursor: '' })),97getWithMetadata: vi.fn(),98} as unknown as KVNamespace;99100const mockEnv: Env = { MY_KV: mockKV };101const mockCtx: ExecutionContext = {102waitUntil: vi.fn(),103passThroughOnException: vi.fn(),104};105106const response = await worker.fetch(107new Request('http://localhost/test'),108mockEnv,109mockCtx110);111```112113## Binding Access Patterns114115### Lazy Access116117```typescript118// ✅ Access only when needed119if (url.pathname === '/cached') {120const cached = await env.MY_KV.get('data');121if (cached) return new Response(cached);122}123```124125### Parallel Access126127```typescript128// ✅ Parallelize independent calls129const [user, config, cache] = await Promise.all([130env.DB.prepare('SELECT * FROM users WHERE id = ?').bind(userId).first(),131env.MY_KV.get('config'),132env.CACHE.get('data')133]);134```135136## Storage Selection137138### KV: CDN-Backed Reads139140```typescript141const config = await env.MY_KV.get('app-config', { type: 'json' });142```143144**Use when:** Read-heavy, <25MB, global distribution, eventual consistency OK145**Latency:** <10ms reads (cached), writes eventually consistent (60s)146147### D1: Relational Queries148149```typescript150const results = await env.DB.prepare(`151SELECT u.name, COUNT(o.id) FROM users u152LEFT JOIN orders o ON u.id = o.user_id GROUP BY u.id153`).all();154```155156**Use when:** Relational data, JOINs, ACID transactions157**Limits:** 10GB database size, 100k rows per query158159### R2: Large Objects160161```typescript162const object = await env.MY_BUCKET.get('large-file.zip');163return new Response(object.body);164```165166**Use when:** Files >25MB, S3-compatible API needed167**Limits:** 5TB per object, unlimited storage168169### Durable Objects: Coordination170171```typescript172const id = env.COUNTER.idFromName('global');173const stub = env.COUNTER.get(id);174await stub.fetch(new Request('https://fake/increment'));175```176177**Use when:** Strong consistency, real-time coordination, WebSocket state178**Guarantees:** Single-threaded execution, transactional storage179180## Anti-Patterns181182**❌ Hardcoding credentials:** `const apiKey = 'sk_live_abc123'`183**✅** `npx wrangler secret put API_KEY`184185**❌ Using REST API:** `fetch('https://api.cloudflare.com/.../kv/...')`186**✅** `env.MY_KV.get('key')`187188**❌ Polling storage:** `setInterval(() => env.KV.get('config'), 1000)`189**✅** Use Durable Objects for real-time state190191**❌ Large data in vars:** `{ "vars": { "HUGE_CONFIG": "..." } }` (5KB max)192**✅** `env.MY_KV.put('config', data)`193194**❌ Caching env globally:** `const apiKey = env.API_KEY` outside fetch()195**✅** Access `env.API_KEY` per-request inside fetch()196197## See Also198199- [Service Bindings Docs](https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/)200- [Miniflare Testing](https://miniflare.dev/)