Backend for Frontend (BFF)
One API can’t serve a mobile app, a web app, and a smart TV well. A BFF gives each client its own
tailored backend, owned by the team that owns the client.
Prerequisites: API Gateway, Service Decomposition
Time to read: ~16 minutes
The problem
You have microservices and several clients — a mobile app, a web SPA, a partner API, a TV app. They
share a general-purpose API.
Why one API can’t serve all of them well:
- 📱 Mobile is on a cellular network with limited bandwidth and battery. It wants few, small,
aggregated responses — one call that returns exactly what the screen needs.
- 💻 Web has more bandwidth and richer screens. It can make more calls and wants more data.
- 📺 TV has a completely different navigation model and a different subset of features.
- 🤝 Partners need a stable, versioned, documented contract that changes rarely.
A shared API serving all four ends up as a compromise that serves none of them well:
GET /users/42 → returns 60 fields
Mobile needs 5 of them, over-fetches the other 55 on a slow network.
Web needs 20.
The home screen then makes 6 more calls to assemble one view → 6 round trips on cellular.
🚨 The two failures here are over-fetching (too much data per call) and under-fetching (too many
calls per screen), and one shared API forces both on somebody.
The pattern
Give each client type its own backend, shaped exactly for it.
flowchart TB
M[Mobile app] --> BM[Mobile BFF]
W[Web app] --> BW[Web BFF]
T[TV app] --> BT[TV BFF]
BM --> S1[Users]
BM --> S2[Orders]
BM --> S3[Recommendations]
BW --> S1
BW --> S2
BW --> S3
BT --> S1
BT --> S2
Each BFF:
- Aggregates the downstream calls its client needs (fanning out in parallel, in-datacenter).
- Shapes the response to exactly what that client renders — no over-fetching.
- Is owned by the team that owns the client.
🚨 That last point is the whole idea, and it’s what distinguishes a BFF from an API gateway. The
mobile team owns the mobile BFF. When they change a screen, they change their BFF — no ticket to a
platform team, no coordination, no waiting.
📐 The mobile home screen goes from 6 cellular round trips (300+ ms each) to 1, with the fan-out
to 6 services happening in-datacenter (~1 ms each, in parallel). That’s the difference between a
sluggish app and a snappy one.
BFF vs API gateway
🚨 The distinction interviewers probe, and getting it right shows you understand both.
| |
API Gateway |
BFF |
| How many |
One, shared |
One per client type |
| Owned by |
Platform team |
The client team |
| Concerns |
Auth, rate limiting, routing, TLS — generic |
Aggregation and response shaping — client-specific |
| Logic |
Minimal, cross-cutting |
Client-tailored, may have real logic |
| Changes |
Rarely |
With the client |
They compose. A common setup: a shared gateway handles auth, rate limiting, and TLS; behind it, a
BFF per client handles aggregation and shaping.
Client → API Gateway (auth, rate limit) → BFF (aggregate, shape) → services
🎙️ “An API gateway centralizes generic concerns — auth, rate limiting — for everyone. A BFF is
per-client and owned by the client team, and it does aggregation and shaping. I’d use a gateway for
the cross-cutting stuff and a BFF per client for the client-specific stuff.”
Why not put aggregation in the gateway?
You can, and it’s a common temptation. The reason not to is ownership.
🚨 A shared gateway doing per-client aggregation becomes a bottleneck: every client team must file
changes against infrastructure owned by a platform team, and that team becomes the constraint on
every client team’s velocity. It also becomes a god object — a single component that knows about
every client’s every screen.
The BFF pushes that logic to the team that has the context and the incentive to get it right. The
mobile team knows what the mobile home screen needs; the platform team doesn’t and shouldn’t have to.
This is Conway’s Law applied:
the architecture (one backend per client) mirrors the org (one team per client).
What it costs
⚖️ BFFs are not free, and the main cost is duplication.
1. Code duplication. The mobile BFF and the web BFF both call the users service, both handle auth,
both do error handling. Some of that is genuinely different (shaping); some is copy-paste.
Mitigation: a shared library for the common parts (downstream clients, auth helpers, error
formatting), keeping only the client-specific shaping in each BFF. Don’t over-share, though —
premature abstraction across BFFs recreates the coupling you were escaping.
2. More services to deploy and operate. Three BFFs is three more things with pipelines, monitoring,
and on-call.
3. A risk of business logic leaking in. 🚨 A BFF should aggregate and shape, not own business
rules. If the mobile BFF and web BFF start computing prices differently, you have a bug factory. Keep
domain logic in the services; keep the BFF thin.
4. Another hop. One more network call in the path. Usually negligible in-datacenter.
5. Proliferation. One BFF per client is reasonable. One BFF per screen is not — you’ve
re-fragmented into nano-services.
The alternatives
GraphQL is the most important alternative, and often the better one.
Instead of a hand-built BFF per client, expose a GraphQL endpoint and let each client request
exactly the fields and relationships it needs in one query.
# Mobile asks for little; web asks for more — same endpoint, no server change
query { user(id: 42) { name, avatar, recentOrders(first: 3) { total } } }
⚖️ GraphQL vs BFF:
- ✅ GraphQL: no per-client backend to build; clients self-serve their shape; one schema.
- ❌ GraphQL: N+1 resolver problems, caching is harder
(every query is different), query-cost control is a real concern (a malicious deep query), and it’s
another technology to operate.
- ✅ BFF: full control, simple caching, ordinary REST.
- ❌ BFF: a backend per client to build and maintain.
→ GraphQL
🎙️ “If clients need very different shapes and change frequently, GraphQL lets them self-serve
without a backend per client. If the shaping involves real orchestration logic — combining several
services with fallbacks and business rules — a BFF gives more control. I’d lean GraphQL for
data-fetching flexibility, BFF for orchestration-heavy clients.”
Other options: field selection on REST (?fields=name,avatar — poor man’s GraphQL), or the
API composition done at a gateway (with the ownership caveat
above).
When to use it
Good fits:
- Multiple client types with genuinely different needs (mobile + web + TV + partner).
- Mobile clients on constrained networks where round trips and payload size hurt.
- Client teams that want to move independently of a platform team.
- Aggregation with real orchestration — several services, fallbacks, conditional logic.
Poor fits:
- A single client type. One backend, no BFF.
- Clients with near-identical needs. The duplication isn’t repaid.
- Very small teams — the operational cost of N backends outweighs the benefit.
- When GraphQL would let clients self-serve their shape.
🎙️ “With one web client, I wouldn’t build a BFF — it’s just the backend. The pattern earns its cost
when several client types have genuinely divergent needs and separate teams.”
⚖️ Trade-offs
| Decision |
Gain |
Cost |
| BFF per client |
Tailored responses; client-team ownership; fewer round trips |
Duplication; more services; risk of logic leaking in |
| One shared API |
Simple, one thing to run |
Over-fetching and under-fetching for everyone |
| Aggregation in the gateway |
One place |
Platform team becomes a bottleneck; god object |
| GraphQL |
Clients self-serve their shape; no per-client backend |
N+1, caching, query-cost control, new technology |
In the real world
- The pattern originated at SoundCloud and was popularized by Netflix, which built device-specific
APIs because a smart TV, a phone, and a browser have radically different capabilities and network
conditions. Netflix’s move from one API to per-device BFFs is the canonical case study, and it came
directly from the over-fetch/under-fetch problem.
- Netflix later moved toward a GraphQL federation model (their “Studio Edge” / federated GraphQL
work), which is a useful signal that GraphQL and BFFs are points on a spectrum, and that large
orgs often evolve from hand-built BFFs toward a federated schema as the number of clients grows.
- The “BFF becomes a monolith” failure is real: teams start with a thin aggregation BFF and
gradually move business logic into it until it’s a second implementation of the domain. Keeping the
BFF thin requires ongoing discipline.
🚨 Interview traps
- Building a BFF for a single client. That’s just the backend.
- Confusing a BFF with an API gateway. Gateway = shared, generic, platform-owned. BFF =
per-client, specific, client-team-owned.
- Putting business logic in the BFF. It aggregates and shapes; the services own the rules.
- One BFF per screen. Nano-service fragmentation.
- Not mentioning GraphQL as the alternative when clients need flexible shapes.
- Ignoring the duplication cost across BFFs.
🎙️ Soundbites
- “The mobile home screen needs six services’ data. Rather than six cellular round trips, a mobile
BFF fans out in-datacenter and returns exactly what the screen renders in one call.”
- “A BFF is per-client and owned by the client team — that’s what distinguishes it from a gateway.
It means the mobile team ships screen changes without filing a ticket against shared
infrastructure.”
- “I’d keep the BFF thin — aggregation and shaping only. The moment it starts computing business
logic, the mobile and web BFFs will diverge and we’ll have two subtly different implementations.”
- “If the clients mainly need different data shapes, I’d consider GraphQL so they self-serve rather
than building a backend per client. A BFF wins when there’s real orchestration to do.”
🛠️ Try it
1. Measure the round-trip cost. Build a screen that needs data from three services. Fetch it two
ways: the client makes three calls directly, or the client makes one call to a BFF that fans out in
parallel. Simulate a 200 ms cellular RTT on the client side. The difference — 600+ ms vs ~250 ms —
is the whole argument.
2. Build two BFFs, then feel the duplication. A mobile BFF and a web BFF over the same three
services. Notice how much is shared (downstream clients, auth) and how much is genuinely different
(the response shape). Extract the shared part into a library and keep only the shaping per BFF.
3. Compare with GraphQL. Expose the same three services behind a GraphQL layer and let a client
request its exact shape. Compare the code you had to write, and then deliberately write an
N+1-triggering query and watch the resolver problem appear.
Check yourself
1. What's the difference between a BFF and an API gateway?
An **API gateway** is a single shared entry point, owned by a platform team, handling generic
cross-cutting concerns — authentication, rate limiting, TLS termination, routing — the same way for
every client. A **BFF** is one backend *per client type*, owned by the *client* team, handling
client-specific aggregation and response shaping. The defining difference is ownership and
specificity: the gateway centralizes what's common; the BFF decentralizes what's client-specific to
the team with the context to get it right. They compose — a gateway in front for auth and rate
limiting, a BFF behind it per client for shaping.
2. Why not just put aggregation logic in the shared API gateway?
Because it recreates the bottleneck the pattern is meant to remove. A shared gateway doing per-client
aggregation must be changed by a platform team every time any client team changes a screen — so that
platform team becomes the constraint on every client team's velocity, and the gateway becomes a god
object that knows about every client's every view. The BFF pushes aggregation to the team that owns
the client, which has both the context (they know what the screen needs) and the incentive (their own
velocity). It's Conway's Law applied deliberately: one backend per client mirrors one team per client.
3. What are the over-fetching and under-fetching problems?
**Over-fetching** is receiving more data than the client needs — a `/users/42` endpoint returning 60
fields when the mobile screen uses 5, wasting bandwidth and battery on a cellular connection.
**Under-fetching** is a single endpoint not providing enough, forcing the client to make many calls
to assemble one screen — the home view making 6 sequential round trips, each 200–300 ms on cellular.
A single general-purpose API forces both problems on some clients, because it's a compromise shaped
for none of them. A BFF (or GraphQL) fixes both by shaping responses to exactly what each client
renders and aggregating the fan-out server-side.
4. When is GraphQL a better choice than a BFF?
When clients mainly need *different data shapes* of the same underlying data and change those needs
frequently. GraphQL lets each client request exactly the fields and relationships it wants in one
query, with no per-client backend to build or maintain and one schema to evolve — clients self-serve
their shape. A BFF is better when the per-client work is *orchestration* rather than just shaping —
combining several services with fallbacks, conditional logic, and non-trivial aggregation — where you
want full server-side control. GraphQL's costs (N+1 resolvers, harder caching, query-cost control,
another technology to run) are the price of that flexibility. Large orgs often start with BFFs and
evolve toward federated GraphQL as client count grows.
5. What keeps a BFF from turning into a second copy of your business logic?
Discipline and a clear rule: the BFF aggregates and shapes; the services own the domain rules. A BFF
should call services, combine their responses, and format the result for its client — nothing more.
The failure mode is gradual: a small computation added for convenience, then another, until the
mobile BFF and web BFF each compute prices or eligibility slightly differently and you have two
divergent implementations of the same rule — a bug factory. Prevention: keep all domain logic in the
services (so there's one source of truth), review BFF changes for logic creep, and share the truly
common infrastructure (downstream clients, auth) via a library while keeping only shaping per BFF.
Further reading