Files
claude-skills/cloudflare/references/bindings/patterns.md
2026-01-30 03:04:10 +00:00

4.9 KiB

Binding Patterns and Best Practices

Service Binding Patterns

RPC via Service Bindings

// auth-worker
export default {
  async fetch(request: Request, env: Env) {
    const token = request.headers.get('Authorization');
    return new Response(JSON.stringify({ valid: await validateToken(token) }));
  }
}

// api-worker
const response = await env.AUTH_SERVICE.fetch(
  new Request('https://fake-host/validate', {
    headers: { 'Authorization': token }
  })
);

Why RPC? Zero latency (same datacenter), no DNS, free, type-safe.

HTTP vs Service:

// ❌ HTTP (slow, paid, cross-region latency)
await fetch('https://auth-worker.example.com/validate');

// ✅ Service binding (fast, free, same isolate)
await env.AUTH_SERVICE.fetch(new Request('https://fake-host/validate'));

URL doesn't matter: Service bindings ignore hostname/protocol, routing happens via binding name.

Typed Service RPC

// shared-types.ts
export interface AuthRequest { token: string; }
export interface AuthResponse { valid: boolean; userId?: string; }

// auth-worker
export default {
  async fetch(request: Request): Promise<Response> {
    const body: AuthRequest = await request.json();
    const response: AuthResponse = { valid: true, userId: '123' };
    return Response.json(response);
  }
}

// api-worker
const response = await env.AUTH_SERVICE.fetch(
  new Request('https://fake/validate', {
    method: 'POST',
    body: JSON.stringify({ token } satisfies AuthRequest)
  })
);
const data: AuthResponse = await response.json();

Secrets Management

# Set secret
npx wrangler secret put API_KEY
cat api-key.txt | npx wrangler secret put API_KEY
npx wrangler secret put API_KEY --env staging
// Use secret
const response = await fetch('https://api.example.com', {
  headers: { 'Authorization': `Bearer ${env.API_KEY}` }
});

Never commit secrets:

// ❌ NEVER
{ "vars": { "API_KEY": "sk_live_abc123" } }

Testing with Mock Bindings

Vitest Mock

import { vi } from 'vitest';

const mockKV: KVNamespace = {
  get: vi.fn(async (key) => key === 'test' ? 'value' : null),
  put: vi.fn(async () => {}),
  delete: vi.fn(async () => {}),
  list: vi.fn(async () => ({ keys: [], list_complete: true, cursor: '' })),
  getWithMetadata: vi.fn(),
} as unknown as KVNamespace;

const mockEnv: Env = { MY_KV: mockKV };
const mockCtx: ExecutionContext = {
  waitUntil: vi.fn(),
  passThroughOnException: vi.fn(),
};

const response = await worker.fetch(
  new Request('http://localhost/test'),
  mockEnv,
  mockCtx
);

Binding Access Patterns

Lazy Access

// ✅ Access only when needed
if (url.pathname === '/cached') {
  const cached = await env.MY_KV.get('data');
  if (cached) return new Response(cached);
}

Parallel Access

// ✅ Parallelize independent calls
const [user, config, cache] = await Promise.all([
  env.DB.prepare('SELECT * FROM users WHERE id = ?').bind(userId).first(),
  env.MY_KV.get('config'),
  env.CACHE.get('data')
]);

Storage Selection

KV: CDN-Backed Reads

const config = await env.MY_KV.get('app-config', { type: 'json' });

Use when: Read-heavy, <25MB, global distribution, eventual consistency OK
Latency: <10ms reads (cached), writes eventually consistent (60s)

D1: Relational Queries

const results = await env.DB.prepare(`
  SELECT u.name, COUNT(o.id) FROM users u
  LEFT JOIN orders o ON u.id = o.user_id GROUP BY u.id
`).all();

Use when: Relational data, JOINs, ACID transactions
Limits: 10GB database size, 100k rows per query

R2: Large Objects

const object = await env.MY_BUCKET.get('large-file.zip');
return new Response(object.body);

Use when: Files >25MB, S3-compatible API needed
Limits: 5TB per object, unlimited storage

Durable Objects: Coordination

const id = env.COUNTER.idFromName('global');
const stub = env.COUNTER.get(id);
await stub.fetch(new Request('https://fake/increment'));

Use when: Strong consistency, real-time coordination, WebSocket state
Guarantees: Single-threaded execution, transactional storage

Anti-Patterns

Hardcoding credentials: const apiKey = 'sk_live_abc123'
npx wrangler secret put API_KEY

Using REST API: fetch('https://api.cloudflare.com/.../kv/...')
env.MY_KV.get('key')

Polling storage: setInterval(() => env.KV.get('config'), 1000)
Use Durable Objects for real-time state

Large data in vars: { "vars": { "HUGE_CONFIG": "..." } } (5KB max)
env.MY_KV.put('config', data)

Caching env globally: const apiKey = env.API_KEY outside fetch()
Access env.API_KEY per-request inside fetch()

See Also