microservice.guru
← All posts

Backend for Frontend: The Missing Link

· Architecture · Microservices · BFF · Mobile

By Alain Cyr, Founder, Koesion

microservice.guru — Backend for Frontend: the missing link. Eighteen round trips to draw one screen, versus one call to a BFF.

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.

One screen, eighteen round trips

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:

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:

The knowledge moves

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:

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.

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.

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 BFF per experience

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

  1. 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;
  2. 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;
  3. If you deleted it tonight, would the business lose a rule? The answer must be no. A BFF holds shapes, not decisions;
  4. 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;
  5. 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.

What belongs in a BFF

The list on the right is not style. It is what turns a BFF into the next monolith.

Do's

Dont's

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:

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

References