Skip to content
IRC-CodingIRC-Coding
API Gateway PatternsBackend for FrontendBFFAPI CompositionProtocol TranslationAPI AggregationCircuit BreakerStrangler FigMicroservices

API Gateway Patterns: BFF, Composition & Protocol Translation

Master API Gateway patterns: BFF, API Composition, Protocol Translation, Circuit Breaker, and Strangler Fig with code examples.

S

schutzgeist

13 min read
API Gateway Patterns: BFF, Composition & Protocol Translation

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:

  1. Backend for Frontend (BFF): Dedicated gateway per client type
  2. API Composition / Aggregation: Multiple backend calls within a single request
  3. Protocol Translation: Protocol conversion at the gateway
  4. Circuit Breaker: Protection against cascading failures
  5. Strangler Fig: Gradual migration from legacy APIs
  6. API Versioning: Version management and parallel API versions
  7. 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:

  1. The gateway initially routes all requests to the old API (façade).
  2. New endpoints are built in the new system. The gateway routes these to the new system.
  3. Endpoints are progressively migrated.
  4. 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) to firstName (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_name extracts 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?

BFF means each client type (web, mobile, B2B) gets its own API gateway or gateway configuration tailored to its specific needs. The web BFF delivers full data, the mobile BFF reduces fields, and the B2B BFF provides batch endpoints. Each BFF can be developed and deployed independently.

2. What is API Composition?

API composition (or aggregation) means the gateway splits a single client request into multiple backend requests, collects the results, and returns them as one response. The client makes one roundtrip instead of several. This reduces latency through parallel calls and simplifies client code.

3. What is Protocol Translation at the API Gateway?

Protocol translation means the gateway converts an incoming protocol to another. For example, REST requests from external clients are forwarded internally as gRPC, or legacy SOAP services are exposed as REST to the outside. This lets external clients use simple protocols while internal systems use efficient ones.

4. How does a Circuit Breaker work?

A circuit breaker has three states: Closed (requests are forwarded, errors counted), Open (once the error threshold is reached, all requests are immediately rejected to spare the backend), and Half-Open (after a timeout, one request is allowed through as a test). This prevents cascading failures when backends are slow or down.

5. What is the Strangler Fig Pattern?

The Strangler Fig pattern enables gradual migration of a legacy API. The gateway initially routes all requests to the old system. New endpoints are implemented in the new system, and the gateway routes those instead. Over time, all endpoints are migrated. Once complete, the old system is shut down.

6. How many BFFs should you have?

Rule of thumb: one BFF per client type, not per device or feature. Typically you’ll have three BFFs: web (browser apps), mobile (iOS/Android), and B2B (partner integrations). More BFFs create duplication and maintenance burden. Fewer force different clients into the same shape.

7. What happens in composition if a backend fails?

When a backend fails, the gateway should return fallback values and mark the response with metadata (e.g., _meta.ordersAvailable: false). The client gets a partial response instead of an error. Circuit breakers prevent waiting for timeouts. The partial response enables graceful degradation on the client side.

8. Should the gateway contain business logic?

No, the gateway should not contain business logic. It can handle transformation, aggregation, authentication, and routing, but not business calculations or validation rules. Business logic in the gateway creates hard-to-test code, team dependencies, and architectural degradation.

9. What is the difference between synchronous and asynchronous composition?

Synchronous composition: the client waits for the aggregated response. Asynchronous composition: the client sends the request, receives a correlation ID, and polls or subscribes later for the result. Asynchronous works better for long-running aggregations; synchronous suits quick parallel calls.

10. How do you configure a circuit breaker correctly?

Key parameters: errorThresholdPercentage (typically 50%, when to open), resetTimeout (typically 30s, how long it stays open), timeout (typically 2–5s, when a call counts as failed), and volumeThreshold (typically 5, minimum calls before calculating error rate). Values depend on the service—critical services need faster breakers.

11. What is Request/Response Transformation?

Transformation means the gateway adapts payloads without changing the backend. Examples include reducing fields for mobile clients, renaming fields (snake_case to camelCase), converting XML to JSON, adding security headers, or removing sensitive fields for certain clients. Modern gateways use JSONPath, jq, or Liquid templates.

12. What is API Versioning at the Gateway?

API versioning at the gateway means managing multiple API versions in parallel. /v1/users routes to the old backend, /v2/users to the new one. The gateway can transform versions so a v1 request also works on the v2 backend. This enables migrations without breaking existing clients.

13. What is Graceful Degradation in API Composition?

Graceful degradation means the API continues working during partial backend failures, but with reduced functionality. The gateway provides fallback values for failed services and marks them in the response. The client can display missing data (e.g., “Orders unavailable”) instead of showing an error for the entire page.

14. What is the difference between API Gateway and Service Mesh?

An API gateway manages external traffic with patterns like BFF, composition, and auth. A service mesh (Istio, Linkerd) manages internal communication between microservices with mTLS, retry, and circuit breaking. Modern architectures have both: API gateway outside for clients, service mesh inside for service-to-service communication.

15. What is the most important anti-pattern at the API Gateway?

The most important anti-pattern is using the gateway as a business logic layer. When business calculations, validation rules, or workflow logic end up in the gateway, it becomes hard to test, teams become dependent on each other, and the gateway becomes a bottleneck. The gateway should contain infrastructure and integration logic, not business logic.

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

  1. https://microservices.io/patterns/apigateway.html
  2. https://samnewman.io/patterns/
  3. https://docs.konghq.com/hub/
  4. https://learn.microsoft.com/en-us/azure/api-management/
  5. https://martinfowler.com/bliki/StranglerFigApplication.html

API Development

Books about API design, REST, GraphQL, OpenAPI and API architecture

Designing Data-Intensive Applications von Martin Kleppmann

Designing Data-Intensive Applications von Martin Kleppmann

Bei Amazon ansehen

Affiliate-Link: Bei einem Kauf erhalten wir möglicherweise eine Provision.

API Design Patterns von JJ Geewax

API Design Patterns von JJ Geewax

Bei Amazon ansehen

Affiliate-Link: Bei einem Kauf erhalten wir möglicherweise eine Provision.

Back to Blog
Share:

Related Posts