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 away from clients, improve performance, and orchestrate Microservices. Understanding these patterns equips you to make informed architecture 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 core patterns:
- Backend for Frontend (BFF): Dedicated gateway per client type
- API Composition / Aggregation: Multiple backend calls in a single request
- Protocol Translation: Protocol conversion at the gateway
- Circuit Breaker: Protection against cascading failures
- Strangler Fig: Gradual migration of 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 customized responses
Why This Matters for IT and Exams
API Gateway Patterns are 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 studies, patterns like BFF and Circuit Breaker appear as examples of architectural design patterns. Mastering these patterns is essential for designing scalable, maintainable API landscapes.
Core Concepts in Depth
1. Backend for Frontend (BFF)
The BFF pattern states: each client type gets its own gateway (or dedicated gateway configuration) tailored precisely to its needs.
The problem: a web app, a mobile app, and a B2B client have different requirements. The web app needs full data for complex UIs. The mobile app needs reduced data for slow networks. The B2B client needs batch endpoints and XML. A single gateway cannot optimally serve all these demands.
The solution: three separate BFFs:
- Web-BFF: Complete 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 consolidates multiple backend requests into a single client request. The client sends one request to the gateway, which calls multiple backends in parallel and combines the results.
Example: a client calls /api/dashboard. The gateway simultaneously calls:
/users/42(User Service)/orders?userId=42(Order Service)/notifications?userId=42(Notification Service)
The gateway merges all three responses into a single JSON response. The client makes one roundtrip instead of three.
Benefits: reduced latency (parallel calls), simpler client logic, centralized error handling. Trade-offs: the gateway becomes more complex, and error handling during partial failures grows challenging.
3. Protocol Translation
The gateway converts incoming protocols to internal ones:
- REST to gRPC: External clients send REST; the gateway translates to gRPC for internal Microservices. gRPC is more efficient (Protobuf, HTTP/2) but less accessible to 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
The Circuit Breaker protects the system from cascading failures. It operates in three states:
- Closed: requests pass through normally; failures are counted.
- Open: once an error threshold is exceeded (e.g., 50% failures in 10 seconds), all requests are immediately rejected. The backend is no longer stressed.
- Half-Open: after a timeout period (e.g., 30 seconds), a test request is allowed through. If it succeeds, the breaker returns to Closed. If it fails, the breaker stays Open.
This pattern prevents a slow or failing service from blocking the entire system by forcing clients to wait on timeouts.
5. Strangler Fig
The Strangler Fig pattern enables gradual migration of a legacy API to a new architecture. Named after the strangler fig tree, which slowly envelops and replaces its host.
The process:
- The gateway routes all requests to the old API (facade layer).
- New endpoints are built in the new system; the gateway routes these to the new backend.
- Endpoints are migrated incrementally.
- Once all endpoints are moved, the old API is retired.
Advantage: migration without a big-bang release, rollback-safe, 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 backend and v2 to the new one.
7. Request/Response Transformation
The gateway transforms payloads without modifying the backend:
- Field reduction: mobile clients receive 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 specific clients
Why API Gateway Patterns Matter in Practice
Scenario 1: Mobile App Performance
A mobile app needs three API calls to render a dashboard. Each call has 200ms latency. Total: 600ms plus rendering. With API Composition: one call, parallel backend invocations, total: 250ms. The app feels significantly faster.
Scenario 2: Legacy Migration
A 10-year-old monolith is being migrated to Microservices. A big-bang release is too risky. Using the Strangler Fig pattern, endpoints are migrated incrementally, and the gateway automatically routes to the correct system. Legacy clients notice nothing.
Scenario 3: Preventing Cascading Failures
Service A calls Service B, but Service B is slow. Service A waits for a timeout, consuming threads in the process. Multiple requests pile up, and Service A itself becomes slow. The Circuit Breaker at the gateway detects Service B’s failures, opens the circuit, and Service A receives an immediate error response instead of blocking. The system stays stable.
Real-World Example: Kong API Gateway with Composition and Circuit Breaker
This example demonstrates two patterns in practice: API Composition (one endpoint aggregates multiple backend calls) and Circuit Breaker (protection against backend outages). We’ve chosen these two because they’re the most common in production and solve the biggest architectural challenges: 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 opens
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,
// Flagging 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');
});
Detailed Information
BFF: When and How Many?
Rule of thumb: one BFF per client type, not per device. Typical BFFs include:
- Web BFF: Browser apps (SPA, SSR)
- Mobile BFF: iOS/Android apps
- B2B BFF: Partner integrations
Avoid: creating 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 the response). For asynchronous composition, there are two approaches:
- Request/Reply with WebSocket: The client subscribes to the gateway, which calls 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 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 calculating error rate (typical: 5)
Strangler Fig: Risks and Mitigations
- Duplicate logic: During migration, business logic lives in both the old and new system. Mitigation: Use shared libraries for business logic.
- Routing errors: The gateway sends requests to the wrong backend. Mitigation: Feature flags and canary releases.
- Data consistency: Old and new systems share a database or maintain separate ones. Mitigation: Clear data partitioning or read-model synchronisation.
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 allow Lua scripts for sophisticated transformations
Anti-Patterns
- Gateway as Business Logic Layer: The gateway should not contain business logic. Transformation and aggregation are fine, but not domain calculations or validation rules.
- Too many BFFs: Every team wants their 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 a fallback, the client gets a cryptic error message. 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 during Composition if a backend fails?
8. Should the gateway contain business logic?
9. What’s 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 the context of API Composition?
14. What’s the difference between an API Gateway and a Service Mesh?
15. What’s the most important anti-pattern at the API Gateway?
Next Steps in Your API Learning Path
The next article covers API documentation with Swagger and OpenAPI — how to document your APIs in a clear, machine-readable, and standards-compliant way.
Resources and Further Reading
- 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.



