Backend for Frontend: The Missing Link
· Architecture · Microservices · BFF · Mobile
By Alain Cyr, Founder, Koesion

Some history
A very large insurer asked me to look at their mobile app. Two questions, both asked with a certain amount of frustration: why does it take so long to build, and why is it so slow?
The backend team had done good work. Genuinely. They had spent a lot of time putting a clean domain API layer over systems older than most of the people maintaining them — claims, policies, parties, coverages, documents, payments. Proper resources, proper contracts, proper documentation. They were proud of it, and they had every right to be. Their expectation was reasonable: here is the API, the mobile team just has to call it.
Then I sat next to a mobile developer for an afternoon and watched them build one screen. One screen: the detail of a claim.
To draw it, the app fetched the claim. Then the policy it was attached to. Then the coverages of that policy — the ones in effect on the date of loss, not today's. Then the parties, one call each. Then the documents, the payment history, the labels for the status codes. Each answer told the app what to ask next. I counted between fifteen and twenty round trips to render a single screen.
And that developer — a strong iOS developer — had to know, that a claim in this company lives across three legacy systems, that a coverage code only means something against a policy version, and that a party with the role claimant is not necessarily the insured. That is domain knowledge. It was living in an iOS app. And in the Android app. And it was about to be written a third time for the web.
Nothing was wrong with the API. The API was well designed — for the domain. It simply had nothing to do with a screen.
The missing piece had a name, and it was nowhere in their architecture: the Backend for Frontend.
We put one in. The claim screen went from eighteen calls to one. The app stopped being a distributed system client and went back to being an app, and the mobile backlog started moving again. That is the whole story of this post.
The distance is not the problem. The number of times you cross it is.
The generic API fallacy
Here is the belief, and it is everywhere: if we build one good API, every client will be happy.
It sounds like engineering discipline: one contract, one model, no duplication, nobody arguing about whose needs come first.
But a domain API is shaped around what the business owns, and a screen is shaped around what a human being is looking at. These are not the same shape, and no amount of API design will make them the same shape. So somebody has to do the reshaping — and if you have not decided who, the answer is the client. Always the client.
That reshaping has three names, and you have met all three:
- Over-fetching — the endpoint returns forty fields because some other consumer needed them; the phone downloads them all, on a metered connection, to render four;
- Under-fetching — the endpoint returns exactly one resource, so the screen needs eleven of them;
- Chattiness — the app has to make a call to know what to call next, and the whole thing becomes a state machine written in Swift.
The client is the worst possible place to pay this bill, and mobile is the worst kind of client.
Why mobile makes it hurt so much
On a laptop in the office, twenty calls is a nuisance. On a phone, it is a product defect.
Latency multiplies. A round trip on a mobile network is not one millisecond, it is somewhere between eighty and two hundred — on a good day. Eighteen of them in sequence is more than two seconds of pure waiting before the first pixel, before any server has done any actual work. Move the same calls inside the datacenter and they become one-millisecond hops you can run in parallel.
Every call is a chance to fail. Eighteen calls is eighteen timeouts, eighteen retries, and every combination in between. The mobile developer has to decide what the screen looks like when call number eleven comes back empty — and then decide it again for every screen in the app. That is why the estimates were so large.
You cannot fix a released app. A web page you fix at lunch. A mobile app has a review queue, a release train, and users who will not update for eighteen months. Every wrong shape you ship stays in the field for two years, which tells you exactly where you do not want your orchestration logic to live: in the binary.
The failure mode you will recognize
Here is what a missing BFF looks like from the inside. Check the ones you recognize:
- One screen makes more than five calls to your own backend;
- The mobile developers maintain a document that explains the domain to each other;
- The same assembly logic exists in Swift, in Kotlin and in TypeScript — three implementations, three sets of bugs;
- Half of the API team's backlog is “can you add this field to this endpoint”;
- A field was added to an endpoint “because mobile needed it”, and now the web app receives it too, forever;
- There is a class in the app called something like
ClaimScreenAssembler, and it is nine hundred lines long; - Screens are slow in a way nobody can attribute to any single service;
- The mobile team's sprint depends on the backend team's sprint. Every sprint.
You are not removing the complexity. You are choosing where it lives, and how many copies of it you keep.
So what is a BFF, exactly
A Backend for Frontend is a backend that serves exactly one frontend experience, and belongs to the team that builds that experience.
Two halves in that sentence, and the second one is the half everybody skips.
One experience. The BFF is allowed to be opinionated and narrow. Its API is not a model of the business — its API is the screen: one endpoint per screen, returning exactly the payload that screen renders, in the vocabulary the designer used in Figma. If the domain calls it a partyRole and the screen says “Who was driving”, the BFF is where that translation happens, and it is the only place it happens.
Owned by the frontend team. If the BFF lives in another team's backlog, you have not removed the queue — you have added a hop to it. The whole benefit of the pattern is that the person who needs the field can add the field, this afternoon, without a meeting. A BFF you have to file a ticket against is not a BFF. It is one more service.
The cleanest way I have found to say it, and I say it in every training now: the BFF is not a layer of your architecture. It is the part of your frontend that happens to run on a server.
What it actually does, concretely:
- Aggregates — one call per screen, fanning out in parallel behind the scenes;
- Trims — the phone receives the four fields it draws, not the forty the domain owns;
- Translates — domain language in, screen language out;
- Absorbs versions — the one people underestimate. Version 4.2 of the app asks for a shape the domain no longer has? The BFF keeps serving that shape while the domain services move on. Without it, your legacy is smeared across every service you own;
- Handles failure once — the payment block times out, the BFF returns the screen with that block marked unavailable, the app renders something sensible. Decided once, on the server, not eleven times in two languages;
- Adapts protocol and identity — session or mobile token in front, service credentials behind. It is also how you keep tokens out of the client, which your security people will like more than anything else in this post.
It is not an API gateway
This confusion comes up in almost every room, usually with the platform team, and it matters because the two things fail in opposite directions.
- An API gateway is generic, and there is one of them. Routing, TLS, authentication, rate limiting, WAF, quotas. Owned by the platform team. It knows nothing about your screens, and it must never learn;
- A BFF is opinionated, and there is one per experience. It is stuffed with screen-specific knowledge, and that is the point. Owned by the client team.
They are not alternatives — they stack: the gateway in front doing the cross-cutting work, the BFF behind it doing the screen work.
The test takes five seconds. Ask: the claim screen is missing a field — whose backlog does that go in? If the answer is the platform team, you do not have a BFF. You have a gateway that somebody started filling with business knowledge, and it will become the bottleneck of your platform — the same failure as the shared Customer Service in how to find your microservice boundaries: reuse bought at the price of everybody's independence.
And what about GraphQL?
It comes up immediately, and it is a fair question.
GraphQL solves the technical half beautifully: one request, the client asks for the shape it wants, no over-fetching. If you want to implement a BFF with GraphQL, go ahead — it is a very good fit.
It does not solve the ownership half, and that is the half that decides whether the pattern works. One supergraph, owned by a central platform team, serving every client in the company, is the generic API fallacy again with a nicer query language: same queue, same meetings, same fight about whose field wins — and the orchestration is back in the client, because the client now writes the query.
GraphQL is a way to build a BFF. It is not a reason not to have one.
How many? One per experience
Not one per screen. Not one per platform logo. One per experience — and the practical proxy for “experience” is release cadence and owning team.
- iOS and Android showing the same screens share one mobile BFF: they ship together, they need the same payloads;
- The web SPA gets its own. It deploys ten times a day and its screens are bigger. Share it with mobile and the slowest cadence wins;
- An internal agent desktop is a third experience with a completely different job. Third BFF;
- A partner or public API is not a BFF at all. It is a product: versioned, documented, supported, with a contract you cannot break on a Tuesday. Do not let it move into a BFF, and do not let a BFF grow into it.
The counting rule is the same one I use for services: count your frontend teams and your release cadences, not your screens. Three client teams is three BFFs. Forty screens is still three BFFs.
One team, one cadence, one BFF. A shared BFF is a gateway with ambitions.
How to put one in, concretely
This is not a rewrite and it should not be a project. The first screen takes days.
Step 1 — Start from the screen, not from the API. Take your three worst screens. Sit with the designer and the mobile developer and write down the JSON the screen actually renders. Not the domain model — the screen. That document is your BFF contract, and you will notice at once how small it is next to what the app downloads today.
Step 2 — Move the orchestration out, unchanged. Lift the sequence of calls out of the app and drop it into the BFF exactly as it is. Do not improve it yet. Boring, verifiable, one afternoon — and the app already goes from eighteen calls to one.
Step 3 — Then make it parallel. The calls are now on one machine in one datacenter, and most of them do not depend on each other. Fan them out, and give every one a timeout. This is where two seconds becomes two hundred milliseconds.
Step 4 — Decide the failure story, block by block. For each region of the screen: essential, or degradable? The claim header is essential. The payment history can come back empty with a small “temporarily unavailable”. Decide it once, implement it once.
Step 5 — Give it to the frontend team. Same repository if you can, same backlog if you cannot, same sprint and same deploy button either way. Without this step, the other four were decoration.
Five questions to test a BFF
- Can the frontend team change it and deploy it today, without asking anybody? If no, it is not a BFF. This is the first question, and it is the one that fails most often;
- Does one screen cost roughly one call? If a screen still makes six, the aggregation did not actually happen — you moved the chattiness one hop and kept it;
- If you deleted it tonight, would the business lose a rule? The answer must be no. A BFF holds shapes, not decisions;
- Does it own any data? It should own nothing but a cache. The day it has a table with a primary key, you have a new source of truth that nobody declared;
- Would a second frontend want to use it as it is? Careful here. If yes, either that is genuinely the same experience — merge them — or you are about to turn it into a shared layer, and you will spend the next year regretting it.
“But that is duplication!”
It is, and it is the first objection every time — usually from the most senior person in the room.
The web BFF and the mobile BFF will both know how to assemble a claim. Two pieces of code doing similar-looking things. That offends a certain kind of engineer, and I understand why. But look at what you are actually trading: you duplicate assembly, which is cheap, mechanical and easy to test, so that you do not couple two release cadences, which is expensive and permanent. I will make that trade every time.
The rule that keeps it honest is one line: duplicate the assembly, never duplicate the rule.
If both BFFs merely fetch the same four things and lay them out differently — fine, leave it. If both BFFs have to decide something — whether a claim is eligible, how a premium is computed, what a customer is allowed to see — then stop. That decision does not belong in either of them. You have just found a missing capability in your domain, and the honest fix is a service that owns it. Two BFFs computing the same rule is not duplication you tolerate; it is a boundary that is missing.
The list on the right is not style. It is what turns a BFF into the next monolith.
Do's
- Write the BFF contract from the screen's payload, out loud, with the designer in the room;
- Give it to the team that owns the screen — same backlog, same sprint, same deploy button;
- One BFF per experience, sized by release cadence, not by platform logo;
- Fan out in parallel, and give every downstream call a timeout and a declared fallback;
- Make it the place that absorbs the old app versions, so your domain services stay clean;
- Keep it stateless — cache, never store;
- Measure calls-per-screen and put it on a dashboard. It is the cheapest frontend architecture metric there is, and it tells you the day a BFF stops doing its job;
- Keep it disposable — a good BFF can be thrown away and rewritten in two sprints, and you should be able to say so with a straight face.
Dont's
- Building one shared BFF for mobile, web and partners — that is a gateway with ambitions, and three teams in one queue;
- Putting a business rule in it because deploying there is faster. It is faster. That is exactly the trap;
- Giving it its own database;
- Letting the backend team own it while the frontend team waits for it;
- One BFF per screen — you will have forty of them and no owner for any;
- Calling a BFF from a BFF;
- Letting a BFF read another service's database directly, “just this once, for performance”;
- Using it as a permanent bandage over a domain API nobody dares to change. It will hide the problem beautifully, and the problem will still be there.
Big warning!
The BFF is the easiest place in your architecture to deploy code: no cross-team approval, no contract negotiation, no coordination. That is the entire value of the pattern — and exactly what makes it dangerous.
Because the day somebody needs a rule shipped by Friday and the domain team's next slot is in three sprints, the rule goes in the BFF. It works. Nobody is punished. It happens again the following month.
Give that eighteen months and your thin little BFF holds the eligibility rule, the pricing adjustment and a table “just for the app”. It is now the thing everybody calls and nobody dares to touch: the monolith you escaped from, rebuilt behind your app, where nobody thinks to look for it.
Two guardrails have worked for me, and they cost nothing:
- Review for the
if. Formatting a date is fine. Concatenating a label is fine. Anifon a business value — an amount, a status, an eligibility — is a rule, and it does not live here. Treat it in review the way you treat a hardcoded password; - Ask the disposability question every quarter. Could we throw this BFF away and rebuild it in two sprints? A healthy BFF always answers yes. The quarter the answer becomes no, business logic has moved in — and your job is to move it back out, into the domain service that should have owned it.
And one more thing, since it is the mirror of what I wrote about boundaries: a BFF does not excuse a bad domain API. It buys you the time to fix it, and it protects your users while you do. It does not do the fixing.
One case this does not cover
Everything above assumes a screen that fetches, renders, and then waits for a human to do something. That is the claim detail, and it is almost every screen most of us build. For those, the rule holds without an asterisk: one screen, one call.
There is one family of screens where it stops being good advice — the dense, high-frequency console. A trading screen. A network operations wall. A live logistics map. Fifty instruments across nine widgets, several updates a second, open all day. Aggregate that into a single payload and you get a megabyte that is stale before it renders. Let each widget fetch its own and you hit the browser's six-connection ceiling, where the console blocks itself and nothing in your monitoring says why.
The pattern does not break there either. It changes shape: the BFF stops being something you call and becomes something you are connected to — bootstrap over HTTP, then one multiplexed stream, with the subscription deduplication happening on the server instead of the fan-out. That is a post of its own, and it is the next one.
Where to go next
- How to find your microservice boundaries — because a BFF only stays thin if the services behind it own real capabilities;
- Advanced BFFs for real-time consoles — what changes when the screen never stops moving. Coming shortly.
References
- Phil Calçado, The Back-end for Front-end Pattern (BFF) — where it got its name, at SoundCloud;
- Sam Newman, Pattern: Backends For Frontends — the clearest short write-up, and the one to send to your platform team;
- Chris Richardson, API Gateway / Backends for frontends — where the gateway and the BFF sit relative to each other;
- Sam Newman, Building Microservices — the chapter on user interfaces;
- Matthew Skelton & Manuel Pais, Team Topologies — for the real argument of this post, which is cognitive load, not latency.