Backend for Frontend
A BFF is an API whose consumer is one client experience: the web app or the mobile app, not "clients in general". It buys screen-shaped responses and per-client iteration speed, and costs an extra service per client type — a price not every team should pay.
Frame the contract
API design starts with a consumer, a design question and a guarantee — never with a URL.
The problem a BFF solves
A general-purpose API serving web, mobile and partners simultaneously ends up shaped for none of them — the compromise surface from Consumer-First Design. The web app wants an order list with customer names and thumbnails embedded; mobile wants a third of the fields at a quarter of the payload; each screen change becomes a negotiation with the backend team, whose queue is full of other clients' negotiations.
The BFF pattern gives each client experience its own API layer: a Web BFF and a Mobile BFF, each translating between its client's screens and the underlying capability services. The BFF owns aggregation (one /home call fans out to four services — the mechanics of that fan-out are Composed APIs: Aggregating Other Services), field selection (mobile's payload is built for mobile), and client-specific concerns: session handling for the web, payload budgets and version tolerance for mobile apps that update on the user's schedule.
The organizational half is the part diagrams omit: a BFF works because the *frontend team owns it*. Screen changes stop being cross-team negotiations — the team that changes the screen changes the BFF endpoint in the same PR. That ownership move, more than the topology, is what buys the iteration speed.
What it buys, measured per client
For mobile, the wins are arithmetic. A home screen assembled from five capability calls pays five RTTs (sequential where dependent) on a radio where each costs 100–300ms; the BFF collapses them into one client-facing call whose fan-out happens datacenter-side at ~1ms per hop. Payload drops the same way: the capability responses total 180KB of everything-any-client-might-need; the mobile BFF returns the 14KB this screen renders. On low-end devices, JSON parse time falls with the payload.
For the web, the wins are shape and stability: the BFF absorbs backend churn (a service splits in two; the BFF updates; the frontend never notices) and hosts the web-specific glue — cookie sessions, CSRF handling — that a general API should not carry (see Authentication in the Contract for where auth belongs). For old mobile versions, the BFF is the compatibility shim: version tolerance is implemented once, server-side, instead of begging users to update.
GET /mobile/v2/home HTTP/1.1 Authorization: Bearer <token> X-App-Version: 4.18.2
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: private, max-age=30
{
"greeting": "Morning, Ada",
"active_order": { "id": "ord_42", "status": "shipped",
"eta": "Tomorrow", "thumb": "https://…/t/ord_42.webp" },
"recommendations": [ { "id": "prd_9", "title": "…", "price": "€19.99" } ],
"unread_count": 3
}The bill, and when not to pay it
The costs are real services with real pagers. Each BFF must be deployed, monitored, secured and kept consistent with the others — authorization enforced in every BFF or, better, pushed down to the capability services so a BFF bug cannot widen access (the BFF is *a* boundary, not *the* security boundary — see Authorization Design in the Contract). Logic duplicates: "format an order for display" drifts between Web BFF and Mobile BFF until someone extracts it, and the extraction recreates the shared-service coupling the pattern was escaping.
Skip the pattern when its preconditions are missing. One client type: a general API with task-shaped endpoints does the same job without the extra hop. No team to own it: a BFF owned by the backend team is just another general API with a narrower name. Thin UI over one service: the BFF would be a proxy with opinions. And if the real problem is that every screen needs different *fields* from the same graph rather than different aggregations, client-driven selection (GraphQL: Client-Shaped Queries Over One Schema) attacks that more directly — with its own bill (see What GraphQL Costs).
- Pay for a BFF when: 2+ client experiences with divergent needs, frontend teams able to own a service, and screen iteration blocked on backend negotiation.
- Skip it when: one client type, no owning team, or a thin UI over a single service.
- Guard against: business logic migrating into BFFs (keep them translation and aggregation), and BFF-only authorization (enforce below).
- Watch the count: web + iOS + Android + TV can mean four BFFs; consolidate per experience, not per platform, when the shapes converge.
Key points
- A BFF is an API with exactly one consumer: a specific client experience — its shape, payload budget and release cadence.
- The ownership move is the point: the team that changes the screen changes the API, ending cross-team negotiation per screen.
- Mobile wins are arithmetic (RTTs and KB moved datacenter-side); web wins are shape stability and a home for web-specific glue.
- BFFs are translation and aggregation layers — business rules and authorization must live below them.
- The pattern costs one owned service per experience; with one client type or no owning team, it is overhead with a fashionable name.
Follow the failure
How the contract fails or gets misused, hop by hop — and what it costs when it completes.
- 1Org → API: one general-purpose API serves web, mobile and partners; every response is a compromise.
- 2Mobile team → backend team: requests a slimmer order payload; the ticket ages behind other clients' tickets.
- 3Mobile team → workaround: ships client-side aggregation over five chatty calls; home screen takes 2.4s on LTE.
- 4Org → BFFs: adopts the pattern but backend owns them; the negotiation queue survives with an extra hop added.
- 5BFFs → drift: display logic and authorization checks accumulate in each BFF; a permissions fix ships to web and misses Android.
- Screen latency on real networks scales with capability-call count until an aggregation layer exists somewhere — client-side is the worst place.
- A BFF without a clear owner becomes a second general API: all of the hops, none of the iteration speed.
- Authorization implemented only in BFFs turns every BFF bug into an access-control incident.
Design, observe, evolve
A contract decision is incomplete until you know how you would notice it failing and how it changes later.
- • One BFF per client experience, owned by (or dedicated to) that client's team, with screen-shaped endpoints and explicit payload budgets.
- • Keep BFFs to translation, aggregation and client-specific glue; enforce authorization and business invariants in the capability services below.
- • Version BFF endpoints on the client's release cadence, and make old-app tolerance a stated BFF responsibility.
- • Define the fan-out behavior per endpoint — timeouts, fallbacks, partial data policy — because the BFF inherits every downstream failure (see [[api-composition]]).
- • Requests-per-screen and screen render time per client are the before/after metrics that justify (or indict) the pattern.
- • Monitor BFF fan-out latency against downstream budgets; the BFF's p99 is the composition of its dependencies' tails.
- • Diff the BFFs periodically for duplicated business logic — convergent code in two BFFs belongs in a service below.
- • Backend services split, merge and re-version behind the BFF without client releases — the BFF is the seam that absorbs it.
- • New experiences (TV, watch) start as endpoints on the closest existing BFF and split out only when shape divergence justifies another service.
- • If screens converge or the org shrinks, BFFs can collapse back into one experience API — the migration is server-side and invisible to shipped apps.
- • Each BFF is a deployable with monitoring, on-call and security review — multiply by client types.
- • An extra network hop on every request: ~1ms datacenter-side, plus another tail to manage.
- • Aggregation logic duplicated across BFFs drifts; extracting it recreates shared-service coupling one layer down.