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-dashboard endpoint 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-overview to 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 CustomerOverview view 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 }