Engineering Atlas › System Design Fundamentals

One backend per client: BFF

11 min read · last reviewed 2026-10-17

Open in the interactive app: practice, quiz, mock interview →

In one minute

How it works

Why one API does not fit all clients

A web dashboard on fibre can make ten parallel calls and render a dense table. A phone on a 4G connection with 120 ms round trips wants one call returning a small, pre-shaped payload. A smart TV needs different image sizes and fewer fields. A single general-purpose API either over-fetches for some clients or forces chatty call patterns on others. Teams then add client-specific parameters (?fields=...&platform=ios) until the shared API becomes a negotiation between every client team.

What a BFF does

For a given screen, the BFF calls the necessary downstream services in parallel, applies per-call timeouts, merges the results, drops unneeded fields, formats values for display (currency strings, localised dates), and returns one response. It can also hold client-specific concerns: session handling for a web app with cookies, token exchange for a mobile app, feature flags, and A/B variants of a screen layout.

The BFF as a security boundary

For browser apps the BFF now has a second, standardised job. RFC 10017 (August 2026), the IETF's best current practice for OAuth in browser-based apps, ranks the BFF as the most secure of its three architectures and strongly recommends it for business apps and apps that handle personal data (RFC 10017). The BFF is the confidential OAuth client: access and refresh tokens stay on the server, and the browser holds only a session cookie marked Secure and HttpOnly, ideally SameSite=Strict with a __Host- name prefix. JavaScript injected into the page can still make calls through the BFF while the user is there, but it cannot steal a token to use elsewhere.

What a BFF should not do

It should not own business rules or data. If two BFFs both compute "is this order refundable?", that rule belongs in the Orders service. A useful guideline: a BFF orchestrates and formats; it does not decide. Some duplication of fetch-and-merge code across BFFs is expected; Sam Newman is relaxed about duplication across services, because extracting shared code risks tight coupling that costs more (Sam Newman).

Partial failure

A home screen built from profile, recommendations, notifications, and promotions should not fail because promotions are down. The BFF marks each dependency as required or optional, applies tight timeouts to optional ones (for example 80 ms), and returns a degraded response with defaults. The client renders what it receives, so clients must be built to handle partial responses (Sam Newman). This is where much of the user-perceived reliability is won or lost.

Ownership and placement

The typical layout is: generic API gateway at the edge for authentication and rate limiting, then one BFF per client type, then domain services. GraphQL is a common way to implement a BFF, letting the client team define queries while resolvers call domain services. Microsoft's guidance goes further: if you already run GraphQL with frontend-specific resolvers, or clients make the same requests, a separate BFF may add nothing (Azure Architecture Center).

How many BFFs is an ownership question as much as a technical one. "One experience, one BFF" means iOS and Android can share a mobile BFF when their screens match and one team owns both apps; when separate teams own them, separate BFFs avoid recreating the shared-API bottleneck (Sam Newman).

Down the stack

Layer 1The contract

A BFF endpoint promises one response per screen within a latency budget, for example 300 ms at p99 server time. The response always contains the required parts or fails clearly; optional parts may be missing or defaulted, and the response says which ones degraded. The BFF promises not to make business decisions: given the same downstream data, the web and mobile BFFs must produce the same facts, only shaped differently.

Layer 2The mechanism

The handler starts all independent downstream calls at once (goroutines, CompletableFutures, or Promise.all), each with its own deadline derived from the incoming request's deadline, for example context.WithTimeout(ctx, 80*time.Millisecond) for optional calls. Results are collected as they finish; when the overall budget expires, unfinished optional calls are cancelled and replaced with defaults. GraphQL BFFs add DataLoader-style batching: resolvers that each need one user's data within the same tick are coalesced into a single getUsers(ids) call, preventing N+1 fan-out. DataLoader's cache lives for one request only and is not a substitute for a shared cache (DataLoader). Responses are compressed with gzip or Brotli and often cached per user for a few seconds.

Layer 3The machine

The client's network dominates. A 4G round trip is typically 50 to 150 ms, and radio wake-up from idle can add hundreds more. A new HTTPS connection costs a TCP handshake plus a TLS 1.3 handshake: two round trips before the request can even be sent, and a third before the first response byte. HTTP/3 removes one of them, because QUIC combines the transport and cryptographic handshakes (RFC 9000). TCP then starts with an initial congestion window of 10 segments, 14,600 bytes on a standard 1,500-byte MTU (RFC 6928): a 180 KB response needs roughly four more round trips on a fresh connection, while a 14 KB compressed response fits in one flight. Inside the data centre, the BFF's downstream calls cost 0.2 to 0.5 ms of network each over warm, pooled connections. Moving fan-out from the phone to the server trades several 150 ms round trips for several sub-millisecond ones plus one slow one.

Why it's built this way

  1. Client round trips over mobile radio are the most expensive resource, so many calls are collapsed into one server-side.
  2. Bytes on the wire cost extra round trips through TCP's congestion window, so payloads are trimmed to what the screen uses.
  3. Different devices have different screens and networks, so each experience gets a backend shaped for it.
  4. UI changes happen at the client team's pace, so the team that owns the client owns its backend.

Diagram

flowchart LR
    IOS["iOS app"] --> GW["API gateway (auth, limits)"]
    WEB["Web app"] --> GW
    GW --> MB["Mobile BFF"]
    GW --> WB["Web BFF"]
    MB --> P["Profile service"]
    MB --> R["Recommendations"]
    MB --> N["Notifications"]
    WB --> P
    WB --> R
    WB --> O["Orders service"]

Worked example

A streaming service's mobile home screen originally made 7 API calls: profile, continue-watching, three recommendation rows, notifications count, and a promo banner. On a mobile network with 150 ms round-trip time and a limit of 4 parallel connections, the screen took about 2 round-trip batches plus server time, around 900 ms, and downloaded 180 KB of JSON because each API returned full objects.

The mobile team builds a BFF. It calls all seven services in parallel from the data centre, where each call takes 10 to 40 ms. It trims each title to the 6 fields the tile needs and picks the image URL for the device's resolution. The response is 22 KB compressed and arrives in one request, one or two round trips on a warm connection: server time 60 ms p50, total perceived latency about 260 ms.

For resilience, profile and continue-watching are required; recommendations, notifications, and promos are optional with an 80 ms timeout each. During a recommendations outage, the home screen still loads, showing a cached "popular" row and a degraded flag the app uses to suppress a spinner. The BFF fleet costs about 15 small containers, owned and on-called by the mobile team.

In the wild

Trade-offs

OptionStrengthsWeaknessesChoose when
One general-purpose APISingle codebase, consistent behaviourOver-fetching, chatty clients, cross-team negotiationFew clients with similar needs
BFF per client typePayloads fit each client, client team autonomyMore services, duplicated orchestrationClients differ significantly in screens or networks
GraphQL layerClients pick fields, one schemaQuery cost control, caching is harderMany client variants with overlapping data
Aggregation in gatewayNo extra serviceGateway becomes client-specific and crowdedVery small, simple aggregations

Pitfalls in production

Leadership lens

Check yourself

  1. Why does a BFF reduce latency for mobile clients even though it adds a hop?

    Show answer

    It replaces many high-latency client round trips with one, and makes the fan-out calls inside the data centre where round trips are a few milliseconds.

  2. Two BFFs both decide whether a user may cancel a subscription. What is wrong?

    Show answer

    A business rule is duplicated in presentation-layer services and will drift. The rule belongs in the subscription service, which both BFFs should call.

  3. How should a BFF behave when an optional dependency times out?

    Show answer

    Return the rest of the response with a default or cached value for that part, and signal degradation so the client can render accordingly.

Sources and further study

Primary

Systems docs

Books

Other curricula

Watch

Free resources

Open in the interactive app: practice, quiz, mock interview →