API Gateway & BFF Pattern
Design API gateways and Backend-for-Frontend: request routing, auth aggregation, response shaping, rate limiting, and the BFF pattern for mobile vs web.
An API Gateway is a single entry point for client requests, handling cross-cutting concerns: auth, rate limiting, request routing, and response aggregation. The BFF (Backend-for-Frontend) pattern extends this - a dedicated API layer per client type (mobile, web, third-party) that shapes data to each client's needs.
Use an API gateway for microservices - it prevents each service from re-implementing auth, logging, and rate limiting. Use the BFF pattern when your mobile app and web app have significantly different data needs. Avoid gateway-heavy logic - keep business logic in services.
Workflow
- Configure request routing and load balancingThe gateway routes requests to the correct backend service based on path, method, or headers.
- Centralize authentication and authorizationEach service re-implementing auth creates inconsistency and security risk.
- Implement rate limiting at the gatewayGateway-level rate limiting protects all services without each implementing it separately.
- Implement the BFF patternMobile and web clients have different payload requirements - BFFs prevent over-fetching and under-fetching.
- Add circuit breakers and observabilityCircuit breakers prevent cascade failures when a service is down; observability enables fast debugging.
Output Format
Route-based gateway, JWT validation + forwarded headers, Redis rate limiter with 429 response, BFF per client type with data shaping, circuit breaker with fallback, distributed trace ID propagation.
Examples
BFF aggregation: `async function getDashboard(userId: string): Promise<MobileDashboard> { const [user, orders, notifications] = await Promise.all([userService.getUser(userId), orderService.getRecentOrders(userId, 5), notificationService.getUnread(userId)]); return { name: user.firstName, orderCount: orders.total, unread: notifications.count, thumbnail: user.avatarThumbnailUrl } // mobile-optimized response }`Circuit breaker: `const breaker = new CircuitBreaker(inventoryService.check, { timeout: 2000, fallback: () => ({ inStock: null, error: "Inventory unavailable" }) }); router.get("/api/product/:id/stock", (req, res) => breaker.fire(req.params.id).then(result => res.json(result)))`Rate limit: `const rateLimit = async (userId: string, limitPerMin: number) => { const key = `rl:${userId}:${Math.floor(Date.now() / 60000)}`; const count = await redis.incr(key); if (count === 1) await redis.expire(key, 60); return count <= limitPerMin }`Edge Cases & Gotchas
- Gateway as a single point of failure: deploy the gateway with high availability (multiple instances, health checks, auto-restart). The gateway failing takes down all services
- Versioning at the gateway: the gateway can route `/v1/*` to stable services and `/v2/*` to updated services without clients changing - enables gradual migration
- WebSocket through the gateway: WebSocket connections require special gateway configuration - standard HTTP gateways don't support them. Use dedicated WebSocket gateways (AWS API Gateway WebSocket, Nginx stream)
When to Break These Rules
- For a single monolithic service or a small number of services (2-3), a gateway adds complexity without benefit. Add one when you have 5+ services with cross-cutting concerns that need centralization.
Reference Files
BFF pattern: https://samnewman.io/patterns/architectural/bff/Kong API Gateway: https://docs.konghq.com/AWS API Gateway: https://aws.amazon.com/api-gateway/Opossum circuit breaker: https://github.com/nodeshift/opossum