The Composite / Aggregator / BFF (Backend-for-Frontend) pattern is used when a client needs data that spans multiple backend services or resources, and you want to present it through a single, tailored API endpoint.
Instead of forcing the client to call multiple APIs and stitch the data together, you provide a composite view:
GET /customer-overview/{customerId}GET /account-dashboard/{accountId}GET /order-summary/{orderId}
This is especially common for UI screens or partner-facing APIs that need a lot of related information in one go.
10.1. Overview
Composite / Aggregator / BFF APIs:
- Aggregate data from multiple backend sources (services, databases, external APIs)
- Produce a view model optimized for a specific use case (screen, device, channel)
- Reduce client complexity and chattiness
- Encapsulate composition logic behind the platform, instead of pushing it into every client
They can be:
- Composite APIs – more domain/platform-oriented, reusable by many clients
- BFF APIs – highly tailored to a specific frontend (e.g., mobile app vs admin UI)
10.2. When to Use This Pattern
Use the Composite / Aggregator / BFF pattern when:
- A client needs data from multiple resources to render a screen or workflow step.
- You want to avoid N+1 calls from the client to multiple services.
- Different channels (web, mobile, partner) need slightly different views of the same underlying data.
- You want consistent composition and caching in one place (the platform/API layer).
Examples:
-
GET /customer-overview/{customerId}- Combines customer profile, recent orders, open support tickets.
-
GET /account-dashboard/{accountId}- Combines balances, recent transactions, alerts, limits.
-
GET /partner-dashboard- Combines usage, billing, health metrics for the partner.
10.3. When NOT to Use This Pattern
Avoid this pattern when:
- A simple CRUD or query endpoint is enough.
- The composite becomes a “overly aggregated resource” with everything in it and no clear boundaries.
- You’re hiding bad microservice boundaries behind a composite band-aid instead of fixing them.
- The composite API leaks too much of the internal service topology (tight coupling).
- Aggregation introduces tight runtime coupling and availability issues (one slow service slows everything).
Also avoid:
- Using a BFF endpoint as the only API for a domain, making it hard to build other consumers.
- Overloading a composite with write behavior; it should primarily be read-oriented projections.
10.4. What the Pattern Looks Like
A typical composite endpoint:
GET /customer-overview/{customerId}
Response example:
{
"customer": {
"id": "cus_12345",
"name": "Alice Doe",
"email": "alice@example.com",
"segment": "PREMIUM"
},
"recentOrders": [
{
"id": "or_1001",
"createdAt": "2024-01-10T12:00:00Z",
"totalAmount": 5500,
"status": "SHIPPED"
}
],
"openTickets": [
{
"id": "ticket_2001",
"createdAt": "2024-01-09T09:00:00Z",
"subject": "Refund request",
"status": "OPEN"
}
]
}
Behind the scenes, the API might call:
- Customer Service
- Orders Service
- Support Service
and shape the result into a CustomerOverview view model.
10.5. Anti-Patterns to Avoid
❌ Monolithic “everything API”
- A single
/mega-dashboardendpoint with dozens of optional sections, used for everything. - Hard to evolve and version; payloads are too large and unfocused.
❌ Tight coupling to internal services
- Designing
/customer-overviewto mirror your internal microservice structure. - If one service changes, your composite API breaks frequently.
❌ Embedding write operations in composite
- Composite/BFF endpoints should generally be read-only.
- Mixing lots of side-effectful operations into a composite read makes behavior unclear and error-prone.
❌ No performance or fallback strategy
- A composite endpoint that always calls 10 downstream services synchronously with no caching, timeouts, or partial degradation strategy.
10.6. OpenAPI Example
A lean but complete OpenAPI 3.0.3 document for:
GET /customer-overview/{customerId}- Returning a composite
CustomerOverviewview model
openapi: 3.0.3
info:
title: Customer Overview API - Composite / Aggregator Pattern Example
version: 1.0.0
servers:
- url: https://api.example.com
paths:
/customer-overview/{customerId}:
get:
summary: Get a composite overview of a customer
tags: [CustomerOverview]
parameters:
- in: path
name: customerId
required: true
schema:
type: string
example: "cus_12345"
responses:
'200':
description: Customer overview
content:
application/json:
schema:
$ref: '#/components/schemas/CustomerOverview'
examples:
default:
value:
customer:
id: "cus_12345"
name: "Alice Doe"
email: "alice@example.com"
segment: "PREMIUM"
recentOrders:
- id: "or_1001"
createdAt: "2024-01-10T12:00:00Z"
currency: "USD"
totalAmount: 5500
status: "SHIPPED"
- id: "or_1002"
createdAt: "2024-01-15T09:30:00Z"
currency: "USD"
totalAmount: 3200
status: "PAID"
openTickets:
- id: "ticket_2001"
createdAt: "2024-01-09T09:00:00Z"
subject: "Refund request"
status: "OPEN"
'404':
description: Customer not found
components:
schemas:
CustomerOverview:
type: object
properties:
customer:
$ref: '#/components/schemas/CustomerSummary'
recentOrders:
type: array
items:
$ref: '#/components/schemas/OrderSummary'
openTickets:
type: array
items:
$ref: '#/components/schemas/SupportTicketSummary'
required:
- customer
- recentOrders
- openTickets
CustomerSummary:
type: object
properties:
id:
type: string
example: "cus_12345"
name:
type: string
example: "Alice Doe"
email:
type: string
example: "alice@example.com"
segment:
type: string
example: "PREMIUM"
required:
- id
- name
- email
OrderSummary:
type: object
properties:
id:
type: string
example: "or_1001"
createdAt:
type: string
format: date-time
example: "2024-01-10T12:00:00Z"
currency:
type: string
example: "USD"
totalAmount:
type: integer
example: 5500
status:
type: string
enum: [PENDING, PAID, SHIPPED, CANCELLED]
example: "SHIPPED"
required:
- id
- createdAt
- currency
- totalAmount
- status
SupportTicketSummary:
type: object
properties:
id:
type: string
example: "ticket_2001"
createdAt:
type: string
format: date-time
example: "2024-01-09T09:00:00Z"
subject:
type: string
example: "Refund request"
status:
type: string
enum: [OPEN, IN_PROGRESS, RESOLVED, CLOSED]
example: "OPEN"
required:
- id
- createdAt
- subject
- status
This shows a single composite endpoint that returns data spanning customers, orders, and tickets — without exposing the internal microservices.
10.7. Visualizing the Composite / Aggregator / BFF Pattern (Mermaid)
sequenceDiagram
autonumber
participant C as Client (Web/Mobile)
participant API as Customer Overview API (BFF)
participant CS as Customer Service
participant OS as Orders Service
participant SS as Support Service
Note over C,API: Get customer overview
C->>API: GET /customer-overview/cus_12345
API->>CS: GET /customers/cus_12345
CS-->>API: 200 OK<br/>{ Customer }
API->>OS: GET /orders?customerId=cus_12345&limit=5
OS-->>API: 200 OK<br/>{ items: [Order...] }
API->>SS: GET /tickets?customerId=cus_12345&status=OPEN
SS-->>API: 200 OK<br/>{ items: [Ticket...] }
API-->>C: 200 OK<br/>{ CustomerOverview }