Infrastructure API

API Gateway Patterns
at the Edge

Rate limiting, authentication, request transformation, response caching, and analyticsโ€”building a production API gateway on Cloudflare Workers.

๐Ÿ“– 15 min read January 24, 2026

Every API needs a gateway. Authentication, rate limiting, transformation, cachingโ€”these concerns shouldn't live in your business logic. They belong at the edge, handled before requests hit your services.

This is the API gateway architecture running in production, processing 2+ million requests monthly with sub-50ms overhead.

The Pipeline

Request Processing Pipeline
๐Ÿ“ฅ Request
โ†“
๐Ÿ” Auth
โ†’
โฑ๏ธ Rate Limit
โ†’
๐Ÿ”„ Transform
โ†“
๐Ÿ’พ Cache Check
โ†“ miss
๐ŸŽฏ Backend Service
โ†“
๐Ÿ“Š Analytics
โ†’
๐Ÿ“ค Response

Pattern 1: Authentication Middleware

Validate API keys and JWTs before anything else:

auth-middleware.ts
interface AuthContext { userId: string; tenantId: string; scopes: string[]; plan: 'free' | 'pro' | 'enterprise'; } export async function authenticate( request: Request, env: Env ): Promise<AuthContext | Response> { // Check for API key const apiKey = request.headers.get('X-API-Key'); if (apiKey) { return await validateApiKey(apiKey, env); } // Check for Bearer token const authHeader = request.headers.get('Authorization'); if (authHeader?.startsWith('Bearer ')) { const token = authHeader.slice(7); return await validateJWT(token, env); } return new Response(JSON.stringify({ error: 'unauthorized', message: 'Missing API key or Bearer token' }), { status: 401, headers: { 'Content-Type': 'application/json' } }); } async function validateApiKey(key: string, env: Env) { // Hash key for lookup (never store plain keys) const keyHash = await hashKey(key); // Check cache first const cached = await env.KV.get(`apikey:${keyHash}`, 'json'); if (cached) return cached as AuthContext; // Query database const result = await env.DB.prepare( `SELECT user_id, tenant_id, scopes, plan FROM api_keys WHERE key_hash = ?` ).bind(keyHash).first(); if (!result) { return new Response('Invalid API key', { status: 401 }); } const context: AuthContext = { userId: result.user_id, tenantId: result.tenant_id, scopes: JSON.parse(result.scopes), plan: result.plan }; // Cache for 5 minutes await env.KV.put(`apikey:${keyHash}`, JSON.stringify(context), { expirationTtl: 300 }); return context; }

Pattern 2: Rate Limiting

Protect your backend with sliding window rate limiting:

rate-limiter.ts
export async function rateLimit( auth: AuthContext, env: Env ): Promise<Response | null> { const limiter = env.RATE_LIMITERS.get( env.RATE_LIMITERS.idFromName(auth.tenantId) ); const result = await limiter.fetch('http://limiter/check', { method: 'POST', body: JSON.stringify({ plan: auth.plan }) }).then(r => r.json()); if (!result.allowed) { return new Response(JSON.stringify({ error: 'rate_limit_exceeded', message: `Rate limit exceeded. Retry after ${result.retryAfter}s`, limit: result.limit, remaining: 0, reset: result.resetAt }), { status: 429, headers: { 'Content-Type': 'application/json', 'X-RateLimit-Limit': result.limit.toString(), 'X-RateLimit-Remaining': '0', 'X-RateLimit-Reset': result.resetAt.toString(), 'Retry-After': result.retryAfter.toString() } }); } return null; // Allowed - continue }

Pattern 3: Request Transformation

Normalize requests before they hit your backend:

request-transform.ts
export function transformRequest( request: Request, auth: AuthContext ): Request { const url = new URL(request.url); // Version routing: /v1/users โ†’ /api/users const path = url.pathname.replace(/^\/v\d+/, '/api'); // Build new headers const headers = new Headers(request.headers); // Inject auth context for backend headers.set('X-User-ID', auth.userId); headers.set('X-Tenant-ID', auth.tenantId); headers.set('X-User-Plan', auth.plan); headers.set('X-User-Scopes', auth.scopes.join(',')); // Add request ID for tracing const requestId = crypto.randomUUID(); headers.set('X-Request-ID', requestId); // Strip sensitive headers headers.delete('Authorization'); headers.delete('X-API-Key'); return new Request( `${env.BACKEND_URL}${path}${url.search}`, { method: request.method, headers, body: request.body } ); }

Pattern 4: Response Caching

Cache GET responses at the edge for instant responses:

response-cache.ts
export async function withCache( request: Request, auth: AuthContext, handler: () => Promise<Response>, env: Env ): Promise<Response> { // Only cache GET requests if (request.method !== 'GET') { return await handler(); } // Generate cache key (include tenant for isolation) const url = new URL(request.url); const cacheKey = `cache:${auth.tenantId}:${url.pathname}${url.search}`; // Check cache const cached = await env.KV.get(cacheKey, 'text'); if (cached) { const { body, headers, status } = JSON.parse(cached); return new Response(body, { status, headers: { ...headers, 'X-Cache': 'HIT' } }); } // Cache miss - call handler const response = await handler(); // Only cache successful responses if (response.ok) { const body = await response.text(); const headers = Object.fromEntries(response.headers); await env.KV.put(cacheKey, JSON.stringify({ body, headers, status: response.status }), { expirationTtl: 60 }); // 1 minute TTL return new Response(body, { status: response.status, headers: { ...headers, 'X-Cache': 'MISS' } }); } return response; }

Pattern 5: Analytics & Logging

Track every request for debugging and billing:

analytics.ts
export function logRequest( request: Request, response: Response, auth: AuthContext, startTime: number, ctx: ExecutionContext ) { const log = { timestamp: new Date().toISOString(), requestId: request.headers.get('X-Request-ID'), method: request.method, path: new URL(request.url).pathname, status: response.status, latencyMs: Date.now() - startTime, tenantId: auth.tenantId, userId: auth.userId, plan: auth.plan, cached: response.headers.get('X-Cache') === 'HIT', userAgent: request.headers.get('User-Agent'), country: request.cf?.country, colo: request.cf?.colo }; // Fire and forget - don't block response ctx.waitUntil( Promise.all([ // Send to analytics service sendToAnalytics(log), // Increment usage counter incrementUsage(auth.tenantId, env) ]) ); }

Putting It All Together

gateway.ts
export default { async fetch(request: Request, env: Env, ctx: ExecutionContext) { const startTime = Date.now(); try { // 1. Authenticate const authResult = await authenticate(request, env); if (authResult instanceof Response) return authResult; const auth = authResult; // 2. Rate limit const rateLimitResponse = await rateLimit(auth, env); if (rateLimitResponse) return rateLimitResponse; // 3. Check authorization (scopes) const scopeCheck = checkScopes(request, auth); if (scopeCheck) return scopeCheck; // 4. Transform & route const transformedRequest = transformRequest(request, auth); // 5. Execute with caching const response = await withCache( request, auth, () => fetch(transformedRequest), env ); // 6. Log (async, non-blocking) logRequest(request, response, auth, startTime, ctx); // 7. Add standard headers const headers = new Headers(response.headers); headers.set('X-Response-Time', `${Date.now() - startTime}ms`); return new Response(response.body, { status: response.status, headers }); } catch (error) { return new Response(JSON.stringify({ error: 'internal_error', message: 'An unexpected error occurred' }), { status: 500 }); } } };
Performance Tip
Use ctx.waitUntil() for non-critical operations like logging and analytics. This lets the response return immediately while background work completes.

Gateway Checklist

  • API key and JWT authentication
  • Per-tenant rate limiting with Durable Objects
  • Request transformation and header injection
  • Response caching with tenant isolation
  • Request/response logging for debugging
  • Usage tracking for billing
  • Proper error responses with retry hints
  • Request ID propagation for tracing

An API gateway isn't optional infrastructureโ€”it's the foundation of a secure, scalable API. Build it once, apply it everywhere, and your backend services can focus on business logic.

Related Articles

Building Multi-Tenant SaaS on Cloudflare
Read more โ†’
Zero to Production: CI/CD for Cloudflare Workers
Read more โ†’
28 Cloudflare Workers: Real-Time SaaS Architecture
Read more โ†’

Need an API Gateway?

We build production API gateways on Cloudflare Workers.

โ†’ Get Started
๐ŸŒ™