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

API Gateway Patterns: BFF, Composition & Protocol Translation

Master API Gateway patterns: BFF, composition, protocol translation, circuit breaker, 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 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:

  1. Backend for Frontend (BFF): Dedicated gateway per client type
  2. API Composition / Aggregation: Multiple backend calls in a single request
  3. Protocol Translation: Protocol conversion at the gateway
  4. Circuit Breaker: Protection against cascading failures
  5. Strangler Fig: Gradual migration of 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 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:

  1. The gateway routes all requests to the old API (facade layer).
  2. New endpoints are built in the new system; the gateway routes these to the new backend.
  3. Endpoints are migrated incrementally.
  4. 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) 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 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_name extracts 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?

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

2. What is API Composition?

API composition (also called 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 a single roundtrip instead of many. This reduces latency through parallel calls and simplifies client logic.

3. What is Protocol Translation at the API Gateway?

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

4. How does a Circuit Breaker work?

A Circuit Breaker has three states: Closed (requests are forwarded, failures are counted), Open (once the failure threshold is reached, all requests are rejected immediately to spare the backend), and Half-Open (after a waiting period, a single 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. Initially, the gateway routes all requests to the old system. New endpoints are implemented in the new system, and the gateway redirects traffic to them. Over time, all endpoints migrate. 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. Three BFFs are typical: web (browser applications), mobile (iOS/Android), and B2B (partner integrations). More BFFs create duplication and maintenance burden. Fewer BFFs force different clients into the same shape.

7. What happens during Composition if a backend fails?

When a backend fails, the gateway should provide fallback values and mark the response with metadata (for example, _meta.ordersAvailable: false). The client receives 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 handle transformation, aggregation, authentication, and routing, but not domain calculations or validation. Business logic in the gateway becomes difficult to test, creates dependencies between teams, and degrades the overall architecture.

9. What’s the difference between synchronous and asynchronous composition?

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

10. How do you configure a Circuit Breaker correctly?

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

11. What is Request/Response Transformation?

Transformation means the gateway adjusts payloads without modifying the backend. Examples: reduce fields for mobile clients, rename fields (snake_case to camelCase), convert XML to JSON, add security headers, or remove sensitive fields for specific clients. Modern gateways use JSONPath, jq, or Liquid Templates for this.

12. What is API Versioning at the Gateway?

API versioning at the gateway means it manages 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 against the v2 backend. This enables migrations without breaking changes for existing clients.

13. What is Graceful Degradation in the context of API Composition?

Graceful degradation means the API continues working with reduced functionality when a backend partially fails. The gateway provides fallback values for failed services and marks them in the response. The client can display missing data (for example, “Orders unavailable right now”) instead of breaking the entire page.

14. What’s the difference between an API Gateway and a Service Mesh?

An API Gateway manages external traffic with patterns like BFF, composition, and authentication. A Service Mesh (Istio, Linkerd) manages internal communication between microservices with mTLS, retry, and circuit breaking. Modern architectures use both: API Gateway on the outside for clients, Service Mesh inside for service-to-service communication.

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

The worst anti-pattern is using the gateway as a business logic layer. When domain calculations, validation rules, or workflow logic land in the gateway, it becomes untestable, creates team dependencies, and becomes a bottleneck. The gateway should contain infrastructure and integration logic, not business logic.

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

  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