When several client types — a web application, a mobile app, a partner integration, a smart TV — consume the same services, a single shared API ends up serving none of them well.
Backends for Frontends gives each client type its own backend, owned by the team that owns the client. API composition is the aggregation those backends perform: several service calls combined into one client-shaped response.
The two travel together and are worth separating, because you can do the composition without the per-client split, and often should.
The problem with one API for everyone
A shared API accumulates the union of every client's needs.
- Mobile wants less. A list screen needs three fields; the shared endpoint returns forty because the web application's detail view needs them. On a mobile network that is real, measurable cost.
- Mobile wants fewer round trips. A screen assembled from four services is four requests over high-latency links. The web application, on a fast connection, does not care.
- Every client blocks every other. Changing a field means coordinating with everyone, so nobody changes anything, so the API accretes optional fields and version parameters.
- Nobody owns it. A shared API belongs to a platform team disconnected from any client's needs, and every request queues behind other teams' requests.
Diagram, as text
- Web app leads to Web BFF
- Mobile app leads to Mobile BFF
- Partner leads to Partner API
- Web BFF leads to Orders
- Web BFF leads to Catalogue
- Mobile BFF leads to Orders
- Mobile BFF leads to Catalogue
- Mobile BFF leads to Recommendations
- Partner API leads to Orders
- Partner API leads to Catalogue
Each backend is owned by the client's team, shaped for that client, and free to change at that client's pace. That ownership is the pattern's substance; the topology alone gives you very little.
What a backend for frontends should and should not do
Should: aggregate calls, shape and trim responses, translate protocols, handle client-specific authentication, and hold client-specific caching and pagination decisions.
Should not: contain business rules. A backend that decides eligibility or computes a discount has taken domain logic out of the domain, and it will be wrong differently from the mobile backend that made the same decision separately. Aggregate and shape; never decide.
This is the boundary that determines whether the pattern helps or hurts, and it is the one that erodes first — because a backend for frontends is a convenient place to put a rule and there is always a deadline.
The duplication you accept
Two or three backends will duplicate work: the same authentication, the same aggregation, the same trimming.
Some of this is fine and should be left alone. The whole point is that clients evolve independently, and eliminating the duplication by extracting a shared layer recreates the shared API one level down — the thing the pattern was adopted to escape.
What is worth sharing is genuinely client-independent infrastructure: authentication middleware, service clients, tracing, error mapping. Libraries, not services.
The signal that the split was wrong is duplication that changes together. If a change to the web backend is always accompanied by an identical change to the mobile one, they were never really different clients, and two backends are pure cost.
API composition and its limits
The aggregation itself has constraints worth naming, and they are the same ones covered under scatter–gather.
Fan-out multiplies tail latency. One request becoming five means the response waits for the slowest of five tails. A whole-request deadline and a required-versus-optional classification are mandatory, not refinements.
Composition cannot join efficiently. "Orders for customers in this region, sorted by value" requires fetching orders, fetching customers, filtering and sorting in application memory. This is the operation API composition is worst at, and past a certain size it stops working entirely. The alternative is a read model built for that query — which is CQRS, and this is one of its clearest justifications.
Partial failure needs a policy. Which services are required, what a degraded response looks like, and how it is labelled so nothing caches it as complete.
A composing backend is a new single point of failure for its clients, and it needs the same timeouts, circuit breakers and bulkheads as any other service.
Check your understanding
Common mistakes
A backend per client instance rather than per client type. Five near-identical backends and five pipelines.
Business rules in the aggregating layer. Each client's backend gets its own drifting copy.
Extracting a shared layer to remove duplication. It recreates the shared API one level down.
No whole-request deadline. Fan-out means waiting for the slowest of several tails.
No required-versus-optional classification. Any one service's failure fails the whole response.
Treating the backend as thin and therefore not needing resilience. It is a new single point of failure for its clients.
Cross-service joins by composition at scale. It works in development and fails on production data volumes.
Practice
A company has one API serving a web app, an iOS app, an Android app and three partner integrations. Mobile releases are blocked by API changes and payloads are large. Design the split.
Write your own answer first, on paper or in a note. Then compare.
Show a hint
Count client types, not clients. Ask which of the six genuinely have different needs.
Show a strong answer
Count client types by needs, not by client count. Six consumers, but almost certainly three types:
- iOS and Android are one type. They render the same screens and need the same data. Two backends here would be duplicated work that always changes together — the exact signal that a split was wrong.
- The web application is a second type. Richer screens, fast connections, larger payloads acceptable, and a different release cadence — deployable daily, where mobile waits on app review.
- The three partners are a third type, and a different kind of thing. A partner API is a published contract with external consumers you cannot coordinate with. It needs versioning, deprecation policy and stability guarantees the internal backends do not, and those requirements should not be mixed into a client backend.
So: a mobile backend, a web backend, and a partner API. Three, not six.
Ownership, which is the part that determines whether this works: the mobile team owns the mobile backend and deploys it with their release cycle. Without that, the split adds deployments and keeps the coordination problem — mobile would still be blocked, just by a different queue.
Addressing their two stated complaints:
- Blocked releases: the mobile backend can add a field, reshape a response or add an endpoint without any other consumer's involvement. This is the whole benefit and it should be visible within a sprint.
- Large payloads: the mobile backend trims aggressively and composes screen-shaped responses — one request per screen rather than four. On mobile networks that is the larger of the two wins.
Migration, since the existing API cannot simply be switched off:
- Stand up the mobile backend first, since mobile has the acute pain. Initially it can proxy to the existing API and trim, which is a small, low-risk change that delivers the payload benefit immediately.
- Then move it to call services directly, endpoint by endpoint.
- Keep the old API as the partner API, versioned and frozen, since partners are the consumers who most need stability and least want change.
- The web backend last, because the web app is the least constrained by the current arrangement.
The rule I would write down at the start: these backends aggregate and shape, and contain no business rules. It will be tested within a month by someone who has all the data in hand and a deadline, and having the rule already agreed is what makes that review conversation short.
A mobile backend composes 6 service calls for the home screen. p50 is 180 ms and p99 is 4 seconds. Diagnose and propose fixes.
Write your own answer first, on paper or in a note. Then compare.
Show a hint
A p99 far above p50 with fan-out has a characteristic cause.
Show a strong answer
The gap between p50 and p99 is the diagnosis. With six parallel calls, the response waits for the slowest, so the composite p99 is roughly the probability that any one of six is slow. Even if each service has a 1-in-100 chance of a slow response, the chance that at least one of six is slow is about 6 in 100 — so the composite p99 reflects each service's p85-ish behaviour, not its p99. Fan-out systematically amplifies the tail, and a 20× gap between p50 and p99 is what that looks like.
Confirm it before fixing: per-service latency percentiles, measured at the backend. Two possible shapes and they need different responses — one consistently slow service, or six services each occasionally slow. The second is the fan-out effect; the first is one service's problem wearing a composition costume.
Fixes, in order of value:
- A whole-request deadline — say 500 ms — rather than per-call timeouts. This caps what users experience and is the single change that most improves p99, because it converts an unbounded tail into a bounded one.
- Classify the six as required or optional. A home screen almost certainly needs profile and primary content, and can render without recommendations, promotions or badge counts. Return as soon as the required ones are in; drop the rest with a metric. This is usually the biggest win and it is a product decision as much as a technical one.
- Cancel outstanding calls at the deadline, or six services keep working on results nobody will read — spending capacity that makes the next request slower.
- Load optional sections after first paint. If the client can render without recommendations and fill them in, they leave the critical path entirely. Frequently the best answer, and it is a client change rather than a backend one.
Then reduce the fan-out itself:
- Question whether six services are needed for one screen. Often two of them serve data that changes rarely and could be cached at the backend for minutes, removing them from the fan-out almost entirely.
- Consider a read model if several calls exist only to stitch related data together. A maintained home-screen projection turns six calls into one — the CQRS argument, and this is a legitimate case for it.
What I would monitor afterwards: deadline-hit rate per service. A service regularly cancelled at the deadline is effectively absent from the screen, and nobody notices from success metrics — the request succeeded, the section was just empty.
What I would not do first: add retries. With a tail-latency problem, retries increase load on services that are already slow and usually make p99 worse.
Key takeaways
- Backends for Frontends gives each client type its own backend, owned by that client's team — the ownership is the substance, not the topology.
- API composition is the aggregation those backends perform: several service calls into one client-shaped response.
- Aggregate and shape; never decide. Business rules in a composing layer drift between clients and disappear from the domain.
- One backend per client type, not per client. iOS and Android with identical needs share one.
- Some duplication is the point. Extracting a shared layer to remove it recreates the shared API one level down.
- Fan-out amplifies tail latency: a whole-request deadline, required-versus-optional classification, and cancellation are mandatory.
- Composition cannot do cross-service joins at scale — that is a read model, and one of CQRS's clearest justifications.
- Composition without the per-client split is a valid and cheaper first step.
Further reading
- Sam Newman, Building Microservices, second edition (O'Reilly, 2021) — backends for frontends and the shared-API failure modes.
- Chris Richardson, Microservices Patterns (Manning, 2018) — API composition and its limits versus CQRS.
- Phil Calçado, "The Back-end for Front-end Pattern" (2015).
- Jeffrey Dean and Luiz André Barroso, "The Tail at Scale" (Communications of the ACM, 2013).
- Martin Fowler, "Micro Frontends" (2019) — the client-side analogue of the same ownership argument.