One backend per client: BFF
Open in the interactive app: practice, quiz, mock interview →In one minute
- A Backend for Frontend is a server-side component built for one specific client experience, such as the iOS app, the web app, or a TV app. It aggregates and shapes data from downstream services into exactly what that client's screens need.
- It is usually owned by the same team that owns the client, so UI changes do not wait on a shared API team.
- It reduces round trips and payload size for constrained clients and keeps client-specific logic out of both the generic gateway and the domain services.
- The risks are duplicated logic across BFFs, a growing number of services to run, and BFFs quietly turning into domain services.
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
- Client round trips over mobile radio are the most expensive resource, so many calls are collapsed into one server-side.
- Bytes on the wire cost extra round trips through TCP's congestion window, so payloads are trimmed to what the screen uses.
- Different devices have different screens and networks, so each experience gets a backend shaped for it.
- 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
- SoundCloud, 2011-2015: where the name came from (engineering write-up). SoundCloud's own web and iOS apps called the same public API as third-party developers. Simple screens needed many fine-grained calls, features could not be exclusive to the official apps without OAuth-scope checks everywhere, and every endpoint change risked breaking outside clients. Client teams were given their own backends, a name coined by the web tech lead, and a profile page that took several calls (
/tracks/1234.json,/users/86762.json,/users/me.json) became one call to/user-profile/123.json. With about five BFFs in production, each re-implemented profile merging slightly differently, and the team took that duplication as the signal to build a sharedUserProfileService(Phil Calçado). - Peloton, 2021: client-facing APIs that trusted the client. Researchers found a REST endpoint and GraphQL gateways serving Peloton's apps that answered unauthenticated requests with riders' user IDs, location, age, gender and workout stats, including for private profiles. Reported on 20 January, the first silent fix in February only required a login, which anyone could create, so other users' data stayed visible; the issues were largely fixed within 7 days of press contact in May. A backend shaped for one client still has to enforce authorisation on every field it returns (Pen Test Partners).
Trade-offs
| Option | Strengths | Weaknesses | Choose when |
|---|---|---|---|
| One general-purpose API | Single codebase, consistent behaviour | Over-fetching, chatty clients, cross-team negotiation | Few clients with similar needs |
| BFF per client type | Payloads fit each client, client team autonomy | More services, duplicated orchestration | Clients differ significantly in screens or networks |
| GraphQL layer | Clients pick fields, one schema | Query cost control, caching is harder | Many client variants with overlapping data |
| Aggregation in gateway | No extra service | Gateway becomes client-specific and crowded | Very small, simple aggregations |
Pitfalls in production
- Business logic leaking into BFFs. Refund rules or pricing appear in two BFFs and drift. Push rules down into domain services and keep BFFs to orchestration and formatting.
- Serial fan-out. Calls are made one after another, adding latencies. Call independent services in parallel and measure the critical path.
- No partial-failure policy. One optional service failing returns a 500 for the whole screen. Classify dependencies as required or optional, with timeouts and defaults.
- BFF sprawl. A BFF per screen or per minor client variant multiplies services. Draw BFFs per user experience (mobile, web, TV), not per page.
- Unowned BFFs. A platform team owns them, and client teams are blocked again. Give each BFF to the team that owns the client.
- Tokens in the browser. A single-page app keeps OAuth tokens in
localStorage, where any injected script can read and exfiltrate them. Let the web BFF hold the tokens and give the browser anHttpOnlysession cookie instead (RFC 10017).
Leadership lens
- Ask what makes this client different enough to deserve its own backend, and where the shared logic will live.
- Make the client team own and operate the BFF, including on-call. Without ownership, the pattern's main benefit disappears.
- Set latency budgets per screen and require every downstream call to declare required or optional plus a timeout.
- Review periodically for duplicated rules across BFFs and push them into services.
Check yourself
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.
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.
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
- RFC 10017: OAuth 2.0 for Browser-Based Applications: section 6.1 defines the BFF pattern for token handling and its cookie rules.
- RFC 6928: Increasing TCP's Initial Window: the 10-segment initial window behind the "fit the first screen in one flight" rule.
- Phil Calçado: The Back-end for Front-end Pattern: the SoundCloud history of the pattern, including its duplication problem.
Systems docs
- graphql/dataloader: per-request batching and caching for GraphQL resolvers.
- RFC 9000: QUIC: the transport under HTTP/3 and its combined handshake.
Books
- Building Microservices (2nd edition), Sam Newman, chapter 14 "User Interfaces": central aggregating gateway versus BFF, "How Many BFFs?", reuse across BFFs, and GraphQL.
- High Performance Browser Networking, Ilya Grigorik, chapter 8 "Optimizing for Mobile Networks": why radio wake-ups and round trips dominate mobile latency.
Other curricula
- roadmap.sh System Design: lists "Backends for Frontend" as a node beside Gateway Aggregation, taken from the Azure pattern catalogue.
- donnemartin/system-design-primer: does not cover the BFF pattern; its "Microservices" section stops at service boundaries and discovery.
Watch
- Backend for Frontend Pattern in Microservices (Arpit Bhayani): motivation, structure, and trade-offs of the pattern.
Free resources
- Backends For Frontends (Sam Newman): the canonical write-up from the person who named the pattern.
- Backends for Frontends pattern (Azure Architecture Center): when to use it and issues to consider.
- API Gateway pattern (microservices.io): covers the BFF variation of the gateway pattern.
- API gateways (Azure Architecture Center): where gateway responsibilities end and BFF responsibilities begin.