API Gateway Patterns: Backend for Frontend, Composition, Aggregation, and More
API Gateway Patterns are proven architectural patterns implemented at the gateway layer to abstract complexity from clients, improve performance, and orchestrate microservices. Understanding these patterns lets you make informed architectural decisions.
What Are API Gateway Patterns?
API Gateway Patterns are architectural patterns that use the API Gateway as a central hub to solve recurring problems in API architecture. They define how the gateway transforms, aggregates, routes, and secures requests—without requiring clients or backends to implement this logic themselves.
The main patterns include:
- Backend for Frontend (BFF): Dedicated gateway per client type
- API Composition / Aggregation: Multiple backend calls within a single request
- Protocol Translation: Protocol conversion at the gateway
- Circuit Breaker: Protection against cascading failures
- Strangler Fig: Gradual migration from legacy APIs
- API Versioning: Version management and parallel API versions
- Request/Response Transformation: Payload adaptation at the gateway
Who Uses API Gateway Patterns?
- Software architects select patterns based on requirements
- Platform teams implement patterns as gateway configuration
- Backend developers need to understand how the gateway transforms their endpoints
- Frontend developers benefit from aggregated and tailored responses
Why This Matters for IT Education and Exams
API Gateway Patterns form core knowledge for architecture certifications (AWS Solutions Architect, Azure Solutions Architect) and are covered extensively in microservices literature (Sam Newman, Chris Richardson). In professional exams and computer science curricula, patterns like BFF and Circuit Breaker appear as examples of design patterns at the architectural level. Mastering these patterns is essential for designing scalable, maintainable API landscapes.
Core Concepts in Detail
1. Backend for Frontend (BFF)
The BFF pattern says: each client type gets its own gateway (or gateway configuration) tailored to its specific needs.
The problem: a web app, a mobile app, and a B2B client have different requirements. The web app needs complete data for complex UIs. The mobile app needs minimal data for slow networks. The B2B client needs batch endpoints and XML support. A single gateway cannot optimally serve all these demands.
The solution: three separate BFFs:
- Web BFF: Full responses, complex aggregations, CORS
- Mobile BFF: Reduced fields, flat hierarchies, caching, lower rate limits
- B2B BFF: Batch endpoints, XML transformation, mTLS, higher quotas
Each BFF can be developed, deployed, and scaled independently.
2. API Composition / Aggregation
The Composition pattern combines multiple backend requests into a single client request. The client sends one request to the gateway, which calls multiple backends in parallel and merges the results.
Example: a client hits /api/dashboard. The gateway simultaneously calls:
/users/42(User Service)/orders?userId=42(Order Service)/notifications?userId=42(Notification Service)
The gateway combines all three responses into a single JSON response. The client makes one round trip instead of three.
Benefits: lower latency (parallel calls), reduced client complexity, centralized error handling. Trade-offs: the gateway becomes more complex, and error handling for partial failures is harder.
3. Protocol Translation
The gateway converts incoming protocols into internal ones:
- REST to gRPC: External clients send REST; the gateway converts to gRPC for internal microservices. gRPC is more efficient (Protobuf, HTTP/2) but less accessible for external clients.
- SOAP to REST: Legacy SOAP services are exposed as modern REST APIs externally.
- HTTP to WebSocket: The gateway maintains WebSocket connections and communicates internally over HTTP.
- GraphQL to REST: The gateway accepts GraphQL queries and maps them to REST backend calls.
4. Circuit Breaker
Circuit Breaker protects the system from cascading failures. It has three states:
- Closed: Requests are routed normally. Failures are counted.
- Open: Once the error rate exceeds a threshold (e.g., 50% within 10 seconds), all requests immediately return an error response. The backend is no longer stressed.
- Half-Open: After a timeout (e.g., 30 seconds), a single probe request is allowed through. If it succeeds, the breaker returns to Closed. If it fails, it stays Open.
This pattern prevents a slow or failing service from blocking the entire system by making clients wait on timeouts.
5. Strangler Fig
The Strangler Fig pattern enables gradual migration from an old API to a new architecture. It’s named after the strangler fig, which slowly grows around a tree and replaces it.
The process:
- The gateway initially routes all requests to the old API (façade).
- New endpoints are built in the new system. The gateway routes these to the new system.
- Endpoints are progressively migrated.
- Once all endpoints have moved, the old API is decommissioned.
Benefit: no big-bang release, rollback-friendly, low risk.
6. API Versioning at the Gateway
The gateway manages multiple API versions in parallel:
/v1/users→ legacy User Service/v2/users→ new User Service (with extended fields)
The gateway can map versions: a v1 request is transformed so it works with the v2 backend (forward compatibility). Alternatively, the gateway routes v1 to the old system and v2 to the new one.
7. Request/Response Transformation
The gateway transforms payloads without changing the backend:
- Field reduction: mobile client receives 5 fields instead of 20
- Field renaming:
first_name(external) tofirstName(internal) - Format conversion: XML to JSON, snake_case to camelCase
- Header enrichment: security headers, correlation IDs, tenant IDs
- Response filtering: remove sensitive fields for certain clients
Why API Gateway Patterns Matter in Practice
Scenario 1: Mobile App Performance
A mobile app needs 3 API calls to render a dashboard. Each call takes 200ms. Total: 600ms plus rendering time. With API Composition: 1 call with parallel backend requests, total 250ms. The app feels noticeably faster.
Scenario 2: Legacy Migration
A 10-year-old monolith is being migrated to microservices. A big-bang release is too risky. With the Strangler Fig pattern, endpoints migrate one by one; the gateway automatically routes to the right system. Legacy clients see no disruption.
Scenario 3: Preventing Cascading Failures
Service A calls Service B, and Service B is running slow. Service A waits for the timeout, holding onto threads. Multiple requests pile up, and Service A slows down as well. The Circuit Breaker at the gateway detects Service B’s failures, opens the circuit, and Service A gets an immediate error response instead of waiting. The system remains stable.
Real-World Example: Kong API Gateway with Composition and Circuit Breaker
This example demonstrates two patterns in action: API Composition (one endpoint aggregates multiple backend calls) and Circuit Breaker (protection against backend outages). I’ve chosen these because they’re the most common in production and solve the biggest architectural challenge: client complexity and fault resilience.
# kong-patterns.yml
# API Gateway Patterns: Composition + Circuit Breaker + BFF
services:
# --- Backend Services ---
- name: user-service
url: http://user-service.internal:3000
- name: order-service
url: http://order-service.internal:3001
- name: notification-service
url: http://notification-service.internal:3002
# --- BFF: Mobile (reduced data) ---
- name: mobile-bff
url: http://bff-mobile.internal:4000
routes:
- name: mobile-dashboard
paths:
- /mobile/dashboard
strip_path: false
# --- BFF: Web (full data) ---
- name: web-bff
url: http://bff-web.internal:4001
routes:
- name: web-dashboard
paths:
- /web/dashboard
strip_path: false
routes:
# --- Composition Route: /dashboard aggregates 3 services ---
- name: dashboard-composite
paths:
- /api/dashboard
strip_path: false
service: user-service # Fallback, overridden by plugin
plugins:
# --- Circuit Breaker for all services ---
- name: request-termination
service: order-service
config:
status_code: 503
message: "Order Service temporarily unavailable"
# --- Rate limiting per BFF ---
- name: rate-limiting
route: mobile-dashboard
config:
minute: 60
limit_by: ip
- name: rate-limiting
route: web-dashboard
config:
minute: 200
limit_by: consumer
// bff-composition.js
// Backend for Frontend: Dashboard Composition with Circuit Breaker
// Node.js/Express — aggregates User, Orders, and Notifications
const express = require('express');
const axios = require('axios');
const CircuitBreaker = require('opossum');
const app = express();
// Circuit Breaker for each service
const userBreaker = new CircuitBreaker(async (userId) => {
const res = await axios.get(`http://user-service.internal:3000/users/${userId}`, {
timeout: 2000
});
return res.data;
}, {
timeout: 3000,
errorThresholdPercentage: 50,
resetTimeout: 30000
});
const orderBreaker = new CircuitBreaker(async (userId) => {
const res = await axios.get(`http://order-service.internal:3001/orders?userId=${userId}`, {
timeout: 2000
});
return res.data;
}, {
timeout: 3000,
errorThresholdPercentage: 50,
resetTimeout: 30000
});
const notificationBreaker = new CircuitBreaker(async (userId) => {
const res = await axios.get(`http://notification-service.internal:3002/notifications?userId=${userId}`, {
timeout: 2000
});
return res.data;
}, {
timeout: 3000,
errorThresholdPercentage: 50,
resetTimeout: 30000
});
// Fallback functions when circuit is open
userBreaker.fallback(() => ({ id: null, name: 'Unknown', error: 'User Service unavailable' }));
orderBreaker.fallback(() => ({ orders: [], error: 'Order Service unavailable' }));
notificationBreaker.fallback(() => ({ notifications: [], error: 'Notification Service unavailable' }));
// Composition endpoint: /api/dashboard?userId=42
app.get('/api/dashboard', async (req, res) => {
const userId = req.query.userId;
if (!userId) {
return res.status(400).json({
error: 'MISSING_PARAMETER',
message: 'userId is required'
});
}
// Parallel calls with Circuit Breaker
const [user, orders, notifications] = await Promise.all([
userBreaker.fire(userId),
orderBreaker.fire(userId),
notificationBreaker.fire(userId)
]);
// Aggregated response
res.json({
user: user,
orders: orders.orders || [],
orderCount: orders.orders ? orders.orders.length : 0,
notifications: notifications.notifications || [],
unreadNotifications: notifications.notifications
? notifications.notifications.filter(n => !n.read).length
: 0,
// Marking partial outages
_meta: {
userAvailable: !user.error,
ordersAvailable: !orders.error,
notificationsAvailable: !notifications.error,
timestamp: new Date().toISOString()
}
});
});
// Mobile BFF: reduced fields
app.get('/mobile/dashboard', async (req, res) => {
const userId = req.query.userId;
const [user, orders, notifications] = await Promise.all([
userBreaker.fire(userId),
orderBreaker.fire(userId),
notificationBreaker.fire(userId)
]);
// Mobile: only essential fields, flat structure
res.json({
userName: user.name || 'Unknown',
orderCount: orders.orders ? orders.orders.length : 0,
unreadCount: notifications.notifications
? notifications.notifications.filter(n => !n.read).length
: 0
});
});
// Web BFF: full data
app.get('/web/dashboard', async (req, res) => {
const userId = req.query.userId;
const [user, orders, notifications] = await Promise.all([
userBreaker.fire(userId),
orderBreaker.fire(userId),
notificationBreaker.fire(userId)
]);
// Web: all fields, nested structure
res.json({
profile: user,
recentOrders: orders.orders || [],
allNotifications: notifications.notifications || [],
_meta: {
userAvailable: !user.error,
ordersAvailable: !orders.error,
notificationsAvailable: !notifications.error
}
});
});
app.listen(4000, () => {
console.log('BFF listening on port 4000');
});
Key Details
BFF: When and How Many?
Rule of thumb: one BFF per client type, not per device. Typical BFFs include:
- Web-BFF: browser applications (SPA, SSR)
- Mobile-BFF: iOS and Android apps
- B2B-BFF: partner integrations
Not recommended: one BFF per screen or per feature. This leads to BFF proliferation and maintenance overhead.
Composition: Synchronous vs. Asynchronous
The composition shown here is synchronous (the client waits for a response). For asynchronous composition, two variants exist:
- Request/Reply with WebSocket: the client subscribes to the gateway, which calls the backends and pushes results as they become available.
- Event-Driven: the client sends a request, the gateway starts asynchronous processing and returns a correlation ID. The client then polls or subscribes for the result.
Circuit Breaker Configuration
Key parameters:
- errorThresholdPercentage: at what error rate does the breaker open? (typical: 50%)
- resetTimeout: how long does the breaker stay open? (typical: 30s)
- timeout: when is a call considered failed? (typical: 2–5s)
- volumeThreshold: minimum number of calls before error rate is calculated (typical: 5)
Strangler Fig: Risks and Mitigations
- Duplicate logic: During migration, business logic exists in both old and new systems. Mitigation: Use shared libraries for business logic.
- Routing errors: The gateway routes to the wrong system. Mitigation: Feature flags and canary releases.
- Data consistency: Old and new systems may share a database or use separate ones. Mitigation: Clear data partitioning or read-model synchronization.
Transformation: JSONPath and Templates
Modern API gateways support transformations using JSONPath or templates:
- JSONPath:
$.user.first_nameextracts a field - JQ: Complex transformations with jq syntax
- Liquid Templates: Template engine for response transformation (Azure API Management)
- Lua: Kong/OpenResty allows Lua scripts for complex transformations
Anti-Patterns
- Gateway as Business Logic Layer: The gateway should not contain business logic. Transformation and aggregation are fine, but not business calculations or validation rules.
- Too many BFFs: Every team wants its own BFF. This leads to duplication and maintenance overhead.
- Composition without timeouts: If a backend is slow, the client waits indefinitely. Every composition call needs a timeout.
- Circuit Breaker without fallback: Without fallback, the client gets a cryptic error. Define sensible fallback responses.
FAQ: API Gateway Patterns
1. What is the Backend for Frontend (BFF) Pattern?
2. What is API Composition?
3. What is Protocol Translation at the API Gateway?
4. How does a Circuit Breaker work?
5. What is the Strangler Fig Pattern?
6. How many BFFs should you have?
7. What happens in composition if a backend fails?
8. Should the gateway contain business logic?
9. What is the difference between synchronous and asynchronous composition?
10. How do you configure a circuit breaker correctly?
11. What is Request/Response Transformation?
12. What is API Versioning at the Gateway?
13. What is Graceful Degradation in API Composition?
14. What is the difference between API Gateway and Service Mesh?
15. What is the most important anti-pattern at the API Gateway?
Continue Your API Learning Path
The next article covers API documentation with Swagger and OpenAPI — how to document your APIs in a way that’s clear, machine-readable, and standards-compliant.
Sources and Further Resources
- https://microservices.io/patterns/apigateway.html
- https://samnewman.io/patterns/
- https://docs.konghq.com/hub/
- https://learn.microsoft.com/en-us/azure/api-management/
- https://martinfowler.com/bliki/StranglerFigApplication.html
Recommended Books on API Development
API Development
Books about API design, REST, GraphQL, OpenAPI and API architecture
Designing Data-Intensive Applications von Martin Kleppmann
Bei Amazon ansehenAffiliate-Link: Bei einem Kauf erhalten wir möglicherweise eine Provision.
API Design Patterns von JJ Geewax
Bei Amazon ansehenAffiliate-Link: Bei einem Kauf erhalten wir möglicherweise eine Provision.



