Your mobile team is building a product page. The general-purpose API returns dozens of fields the phone never shows, and reviews, stock and the wish list each need another call. Every change to those endpoints waits for an API team that also serves the website and three partners.
A backend for frontend (BFF) gives each client experience its own small server-side API, owned by the team that builds the client. This post covers the pattern's origin, what a BFF should do, how it compares with an API gateway and GraphQL, how it keeps OAuth tokens out of the browser, its pitfalls, and how to draw it.
Where the pattern comes from
SoundCloud named the pattern. Phil Calçado's 2015 write-up describes apps built on the public API, where a profile page called /tracks/1234.json, /tracks/1234/related.json, /users/86762.json and /users/me.json and merged the results itself. Once client teams owned their backends, they moved the merging there and the app asked for GET /user-profile/123.json. The web tech lead, Nick Fisher, coined the term. Calçado sums it up: "The BFF was part of the application."
Sam Newman's Backends For Frontends article from November 2015 generalized it: one backend per user experience, maintained by the team that builds that UI.
Chatty clients and one-size-fits-all APIs
- Chatty clients. Newman's example is a wish list: fetch the list, then names, prices and stock levels for its items. That is two dependent round trips; at, say, 150 ms each on a mobile network, 300 ms pass before rendering starts. With a BFF the phone pays one round trip, and the dependent calls run inside the data center, where a hop costs a few milliseconds at most.
- Payloads sized for the biggest screen. The phone downloads fields it never displays.
- A shared bottleneck. Every client team queues changes to one deployable, often owned by a separate team.
What a BFF is responsible for
| Responsibility | In the BFF | Not in the BFF |
|---|---|---|
| Aggregation | One endpoint per screen, calling services in parallel | Joins every client needs; those belong in a service |
| Response shaping | Picking, renaming and formatting fields | Business rules such as pricing |
| Auth and session | Session cookie, token storage and refresh | Final authorization; services decide |
| Caching | Short-lived aggregated responses | Personalized data in a cache keyed only by URL |
| Feature flags | Which variant this client sees | Flags that change domain behavior |
BFF vs API gateway vs GraphQL
| API gateway | BFF | GraphQL server | |
|---|---|---|---|
| Serves | All clients | One client experience | Any client, through one schema |
| Usually owned by | Platform team | The client team | A graph team, or domain teams through federation |
| Does | TLS, routing, token checks, rate limits | Aggregation, shaping, session and tokens | Lets clients select fields; resolvers call services |
| Changes when a screen changes | No | Yes, in the same release | Often only the query changes |
| Typical failure | Business logic creeps in | Duplication, slow fan-out | Expensive queries, N+1 resolver calls, harder HTTP caching |
A common setup puts one gateway at the edge with BFFs behind it, as in the API gateway architecture diagram. One shared graph can replace per-client BFFs, and Microsoft's pattern guidance notes that with frontend-specific resolvers, separate BFFs may add nothing. The cost is the team boundary: every client team negotiates changes to one schema.
Ownership and team boundaries
The BFF belongs to the client team: the web app and its BFF change in one pull request and ship in one release. Newman notes that a mobile team can even move logic into its BFF to ship without an app store review.
How many BFFs? Newman quotes Stewart Gleadow: one experience, one BFF. REA Group ran separate BFFs for iOS and Android; SoundCloud shared one between its iOS and Android listener apps, and its engineers told Newman they might reconsider. Pete Hodgson's version is to follow team boundaries. Your engineering org chart should show exactly one owning team per BFF.
A partner BFF keeps a stable, versioned contract and maps it onto the services as they change, so internal work does not break partner integrations. Routing partners straight to versioned service APIs also works.
The BFF as a token handler for browser apps
A single-page app that runs OAuth itself keeps tokens in the browser, where any injected script can steal them; the JWT guide lists where a browser app can keep tokens and the risk of each. RFC 10017, OAuth 2.0 for Browser-Based Applications, became a Best Current Practice (BCP 212) in August 2026. It ranks three architectures in decreasing order of security:
| Pattern | Where tokens live | What injected JavaScript can do | RFC 10017 verdict |
|---|---|---|---|
| BFF | Only on the server | Send requests through the BFF while the page is open | Strongly recommended for business and sensitive apps |
| Token-mediating backend | Access token in the browser, refresh token on the server | Steal access tokens and ask the backend for fresh ones | Only when a full BFF is not viable |
| Browser-based OAuth client | All tokens in the browser | Steal every token and run new flows | Not recommended for business or sensitive apps |
Here the BFF is a confidential OAuth client. It runs the authorization code flow with PKCE, exchanges the code with its own credentials, stores the tokens against the user's session and attaches the access token to every call it forwards. The browser holds only a cookie; the OAuth 2.0 flow diagram walks through the code flow.
The RFC's main rules:
- Cookie:
SecureandHttpOnlyare required;SameSite=Strict,Path=/, noDomainand a name prefix such as__Host-Http-are recommended. Chrome and Firefox enforce that prefix; Safari, at the time of writing (November 2026), applies only its__Host-part. - CSRF: a defense is required. A static custom header on every API call forces a CORS preflight for cross-origin requests.
- Proxy: forward only to allowlisted hosts.
- Session state: a server-side store, which the RFC recommends only at small scale, or tokens encrypted in the cookie, within the 4,096 bytes per cookie that RFC 6265 asks browsers to support. Let the session last as long as the refresh token.
Set-Cookie: __Host-Http-sid=3f9c...; Secure; HttpOnly; SameSite=Strict; Path=/
// Same-origin fetch sends the cookie by default.
await fetch("/bff/orders", {
method: "POST",
headers: { "Content-Type": "application/json", "X-CSRF": "1" },
body: JSON.stringify(order),
});
A BFF does not stop cross-site scripting: injected code can still call the BFF while the page is open, so keep a Content Security Policy. Native apps follow RFC 8252 instead, running the code flow with PKCE in the system browser, so a mobile BFF usually just validates a bearer token.
Pitfalls
- Duplicated logic. Newman prefers some duplication to shared code that couples BFFs. When logic appears a third time, extract it: a library for technical code, a domain service for business logic. SoundCloud built a UserProfileService after about five BFFs each assembled user profiles.
- The BFF as a monolith. Warning signs: it owns domain data or business rules, other BFFs call it, or several teams commit to it. A discount computed by the web BFF but not the mobile one is a bug a customer will find.
- Fan-out latency. If a BFF calls five services in parallel, the chance that at least one answers slower than its own p99 is 1 − 0.99^5 ≈ 4.9%: the BFF's p95 is roughly the services' p99. Dean and Barroso's The Tail at Scale covers countermeasures.
- One more hop. Each BFF adds latency, a deployment and an on-call rotation, and services that rate-limit by IP address now see only the BFF's address.
Timeouts, partial responses and circuit breakers
Set a deadline per screen and split it: with 800 ms in the BFF, a 300 ms timeout per call leaves room for one retry. Run independent calls in parallel and pass the deadline along.
Then decide which data is required. Newman's wish list needs the list but can render without stock levels. Return what you have and mark what is missing:
{
"items": [{ "id": "sku-123", "name": "Trail shoe", "price": "$89.00" }],
"stock": null,
"errors": [{ "section": "stock", "reason": "timeout" }]
}
Give each downstream service its own circuit breaker and connection pool, so one slow service fails fast instead of holding every worker; the circuit breaker pattern covers thresholds and fallbacks. Retry only idempotent reads, at one layer, and cache aggregated responses no longer than their freshest part allows.
The BFF architecture diagram
Diagram contents
- Shared services (domain teams)
- Identity provider
- Session store
- Web app
- Web BFF
- Mobile app
- Mobile BFF
- Partner system
- Partner BFF
- Catalog service
- Orders service
- Profile service
- session cookie only
- bearer access token
- client credentials token
- parallel calls + token
- parallel, fewer fields
- stable v1 mapping
- tokens by session ID
- 1. log in (redirect)
- 2. code + PKCE → tokens
How to read it:
- One row per client experience, colored by the owning team. Each client calls only its own BFF.
- Each BFF fans out to the shared services; its arrow ends at the frame because it calls whichever services its screens need.
- Dashed edges are sign-in only. The browser is redirected to the identity provider, then the web BFF exchanges the code for tokens, the same step as in an OpenID Connect sign-in.
- Tokens stay on the server, in the session store under a session ID. An encrypted cookie also works, and RFC 10017 prefers it beyond small deployments.
- The Markdown block lists the cookie settings and an example per-screen budget.
Drawing BFF diagrams clearly
- Give each client a row with its BFF, labeled with the owning team.
- Label what crosses each edge: a session cookie, a bearer token or client credentials.
- Show where tokens live, in a store or an encrypted cookie.
- Draw the gateway separately. One box for the gateway and the BFFs hides ownership.
- Watch for the hub. One BFF box connected to every client is a general-purpose backend.
- Skip the endpoint list. One labeled edge per BFF into a services boundary is enough; a diagram of the microservices behind the BFFs covers the service side.
Build it in Archboard
- Sign in with Google or GitHub and create a diagram.
- From the C4 Model pack, add a Container each for the web app and the mobile app and an External System for the partner, one per row.
- Add a Container next to each client for its BFF, with a technology and a description.
- Add a Frame from the Core pack named Shared services (domain teams), holding three Container nodes: catalog, orders and profile.
- Above the web row, add an Identity Provider from the Security pack and a Redis node from the Data pack as the session store.
- Connect each client to its BFF and each BFF to the frame, label the edges, dash the two sign-in edges and color each row by team.
- Add a Core Markdown block with the cookie settings and the budget, and Core Text labels for the teams.
If your services already live in a Mermaid flowchart, the Mermaid converter gives you an editable first draft. The example uses 13 diagram nodes, within the Free plan.
FAQ
What is the backend for frontend (BFF) pattern? A server-side API built for one client experience, such as a web app, mobile app or partner integration, and owned by that client's team. It aggregates backend calls and shapes the responses.
What is the difference between a BFF and an API gateway? A gateway serves all clients and handles routing, token checks and rate limits. A BFF serves one client and changes with its screens. Many systems put a gateway in front of several BFFs.
Does GraphQL replace the BFF pattern? It can, because clients select the fields they need. Someone still owns the schema and query limits, and a GraphQL server built for one client is itself a BFF.
How does BFF authentication work? The BFF is a confidential OAuth client: it runs the authorization code flow with PKCE, keeps the tokens on the server and gives the browser only an HttpOnly session cookie.
How many BFFs should I have? One per user experience, aligned with your teams. Two apps with the same experience and owning team can share one.