Skip to main content

API Gateway & BFF

Editorial

Introduction

Back in API Gateways: The Front Door, you built the door: one entrance where TLS terminates, requests authenticate, rate limits apply, and traffic routes to the service that owns it. That lesson ended with the door doing its job, and with a quiet teaser that the door could get smarter.

Two things have changed since. First, behind the door there is now a fleet. Microservices — and When to Split broke the application into services, and Service Discovery taught the door to find them as they move. Second, and this is the change this lesson is really about, there is no longer one kind of caller in front of the door. There is a phone on a train with two bars of signal. There is a desktop browser with a screen full of space to fill. There is a TV, a watch, a partner's server. They all want the same product, and they want it in completely different shapes.

So the question this lesson answers: whose shape does the API wear? Keep one general-purpose API and every client pays to reshape it. Give every client its own edge and you have just multiplied the thing you must build and run. The pattern that resolves this tension, the Backend for Frontend, is one of the few in this course that is as much about teams as it is about traffic.

Infographic titled: whose shape does the API wear? The left panel, one size fits all, shows a phone, a desktop and a partner each connecting through bent dashed arrows into a single tall generic API box with one small square socket, captioned: every client bends to fit. The right panel, one edge per client, shows the same three callers with straight arrows: the phone meets its own edge shaped for tiny calls, the desktop meets its own edge shaped rich and wide, while the partner deliberately keeps a dashed generic API chip marked versioned, because strangers need stability rather than fit. A dark band beneath states the lesson: a generic API speaks in nouns, a screen speaks in scenes, and the backend for frontend takes the client's side. A final note repeats that partners stay on the generic door, where stability beats fit.

One Size Fits All, and Its Two Bills

Start with the default nobody chooses on purpose: one general-purpose API that every client consumes. It is what SoundCloud had after breaking up their monolith, one public API serving the web player, iOS, Android, and every third-party developer at once. It worked, and it presented two bills that kept growing.

The technical bill. A general-purpose API speaks in nouns: a track, a user, a comment. A screen speaks in scenes. To draw one user-profile page, the mobile app had to call /tracks/1234, then /tracks/1234/related, then /users/86762, then /users/me, and stitch the results together on the phone. Four round trips over a mobile network to draw one screen, most of the payload discarded because the endpoint returns everything any client might want, not the little this screen shows. The two classic symptoms have names: over-fetching, receiving far more than you render, and under-fetching, needing another round trip because no single endpoint has the whole scene. A phone on a flaky network pays both at once.

The organizational bill, and it is the one that actually forces the change. One API serving everyone means every change is a negotiation with everyone. SoundCloud put it plainly: whenever they added something new, they had to spend real time making sure the new endpoint was not over-specialised for one app, because third parties were watching the same surface. The API team becomes a queue that every client team waits in. An A/B test on the iOS home screen now needs sign-off from people who own a public contract. The general-purpose API is not just a chatty interface. It is a bottleneck team.

Hold both bills in mind, because the pattern that fixes this has to pay down both, and any fix that only addresses the round trips has missed half the problem.

Diagram titled: one screen, four trips. A phone showing a profile page that needs only a name with avatar, a top track and a related strip must send four numbered calls, users me, users 86762, tracks 1234, and tracks 1234 related, each crossing a hatched red band marked mobile network before reaching a violet generic API that is noun-shaped. Each call is a full round trip drawn as an outbound and a return arrow through the slow band. Below, two bars compare the bill: what crossed the radio is a long red bar of 610 kilobytes, while what the screen shows is a short green bar of 45 kilobytes. The closing band names the symptom pair: over-fetch the payload, under-fetch the scene.

The BFF: An Edge That Takes the Client's Side

The fix SoundCloud landed on has a disarmingly literal name: different back-ends for different front-ends. A Backend for Frontend is a small edge service that exists for exactly one client experience, speaks that client's language, and is owned by the team that builds that client.

Watch what happens to the profile screen. Instead of four noun-shaped calls, the iOS app makes one: GET /user-profile/123. The BFF fans out to the track service, the user service, the recommendation service, merges the results, throws away everything the screen will not show, and returns a payload shaped like the screen itself. The four round trips still happen, but they happen behind the edge, datacenter-to-datacenter where a call costs a millisecond, not phone-to-datacenter where it costs a hundred. The phone pays for one.

That is the technical bill paid. The organizational bill is paid by the ownership rule, and the ownership rule is the actual pattern: the client team owns its BFF. The iOS team changes the iOS edge the way they change the app, without negotiating with the Android team, the web team, or a central API committee. SoundCloud's phrase for it is the one to remember: the BFF is part of the application. It is the client's server-side half, not a shared platform component. It even gives the team a door the app store cannot slam: move a decision from the app into the BFF, and you can change it this afternoon instead of waiting a week for release review.

What may live inside a BFF follows from what it is. Composition of downstream calls. Reshaping responses into the client's presentation model. Choosing what to drop when a downstream service is slow. In other words, presentation logic that happens to run on a server. What must not live there is everyone else's business: the moment a BFF contains a rule another client would also need, that rule is domain logic wearing an edge costume, and it belongs in a service below.

How Many BFFs, and Where the Door Duties Go

Two practical questions decide whether this pattern helps you or buries you.

How many BFFs? The rule from the people who ran it: one experience, one BFF. Not one per device model, not one per team that asks. If your iOS and Android apps are the same experience with different paint, they can share a BFF, especially if one mobile team owns both. The moment the experiences diverge, split. What you must never do is let two genuinely different interfaces share a BFF to save effort, because that shared edge starts collecting compromises, and a few quarters later you have rebuilt the general-purpose bottleneck one layer higher.

Where do the door duties go? A BFF is not a replacement for the front door. TLS, authentication, coarse rate limits, the whole pipeline from API Gateways: The Front Door, stays in one shared gateway that sits in front of every BFF, because those concerns are identical for every client and duplicating them per edge is how security holes are born. The shape that results is a stack of three layers: the shared gateway doing cross-cutting enforcement once, the per-experience BFFs behind it doing composition and shaping, and the domain services below doing the actual thinking. Each layer is owned differently: platform team, client teams, domain teams. And the extra moving parts buy a containment wall along the way: edges fail separately, so a bad deploy in the TV BFF breaks the TV experience and nothing else, while a bad deploy in a shared API used to break every client at once.

And the duplication? Five BFFs will inevitably grow five copies of some logic. The people who built the pattern are relaxed about that, deliberately: a little duplicated glue between edges is cheaper than a shared library that couples five teams' release trains together. The line they draw is sharper than "don't repeat yourself": when the duplicated thing is domain logic, formatting a user profile, deciding what a "related track" is, it does not get extracted sideways into a shared edge library. It moves down, into a domain service every BFF calls. That is exactly what SoundCloud did when five BFFs grew five user-profile implementations: they extracted a UserProfileService, and the BFFs went back to being thin. A workable rule of thumb from the same school: tolerate it the second time you write it, extract it the third.

The layered edge drawn as a stack with ownership lanes. Three tinted vertical lanes, each labelled one team's lane, hold a mobile app above a mobile BFF, a web app above a web BFF, and a TV app above a TV BFF, every BFF marked compose and shape. A single shared gateway bar owned by the platform team crosses all three lanes between clients and BFFs, enforcing TLS, auth and rate limits once. Below the BFFs, fan-out lines converge on three amber domain services, users, orders and profiles, each owned by a domain team. Two caption lines state the duplication rule: shared domain logic never spreads sideways across BFFs, it moves down into a service every BFF calls. The dark band closes: three layers, three different owners.

Composition Under a Deadline

Everything above makes the BFF sound like a formatting layer. The hard part is that it is a formatting layer with a deadline, standing in front of a fan-out.

One screen request becomes a handful of downstream calls, three today, five after the next feature. Fire them in sequence and the screen waits for the sum. So a competent BFF fires everything that has no dependency in parallel and waits for the slowest, which is better, and is also the trap: Tail Latency: Living at p99 showed you that the wider the fan-out, the more often somebody in it is having their bad day. The slowest call decides the screen, every time.

So the edge needs a budget. The client is willing to wait, say, 400 milliseconds. That budget flows down exactly the way Timeout Hierarchies prescribed: the BFF gives each downstream call a deadline it can afford, and a call that misses its deadline is not an error to propagate. It is a decision to make.

Which is the real lesson of this section: partial response is a product decision, made in advance. The home screen is user info, orders, and a recommendation rail. If the recommendation service is timing out, the right behaviour is almost never a blank screen with a spinner that gives up. It is the screen, shipped, with the rail missing. Deciding which pieces of a screen are load-bearing and which are droppable is not an exception handler someone writes during an incident. It is a designed property of the edge, agreed with the product team before the first bad day. The wishlist without stock badges beats no wishlist. The profile without the related-tracks strip beats an error page.

This is also where the caller-side discipline you already own comes back: the BFF is a caller like any other, so every downstream call gets the treatment from Calling Services Safely: Timeouts, Retries & Backoff, and a dependency that keeps failing earns a breaker from Circuit Breakers, which conveniently gives the degradation logic a clean trigger: breaker open, rail off, screen ships.

Diagram titled: the fan-out races the budget. A violet BFF box fires three calls at once, drawn as horizontal bars racing rightward against a dashed amber line at 400 milliseconds labelled as coming from the client's patience. User finishes at 60 milliseconds and orders at 90, both green with check marks, while the red recommendations bar is still running when it hits the line and ends in a torn edge marked cut. Two outcome cards follow: ship it, a screen delivered at 400 milliseconds with two rendered rows and one dashed empty slot where the rail was dropped, versus wait for all, a blank screen holding an hourglass for 900 milliseconds because one call held it. The dark band states the rule: partial response is a product decision, made in advance.

The Drill: Design the Edge, Then Break It

You now hold three designs for the same product: the client calls the services itself, one shared composed endpoint serves everyone, or each client gets its own BFF with a budget. The differences stay theoretical until something is slow, so make something slow.

Pick a client, pick an edge design, and fire traffic. Watch the round trips, the payload, and the clock. Then drag the recommendation service into the mud and fire again. One design makes the phone pay, one design makes everyone wait for the straggler, and one design ships the screen with a hole in it. Find which is which, and notice when each design starts to hurt.

Design the edge for each client, fire live traffic, then slow one service down and watch which design ships the screen.

How Much Brain May the Edge Have?

Every composition layer in history has faced the same temptation, and an entire generation of architecture died from giving in to it.

The BFF composes five responses into one. It is right there in the request path, it sees everything, and it deploys in seconds. So the day a deadline looms, somebody proposes: just put the discount rule in the BFF for now. Then the eligibility check. Then the ordering logic. Each step is locally reasonable, and the sum has a name: the Enterprise Service Bus. The generation before microservices routed everything through a smart middleware pipe that accumulated routing rules, transformations, and business logic, until the pipe itself was the most important, least deployable, most shared-fate component in the company. Nobody could version it, nobody fully owned it, and everything depended on it.

The microservices movement wrote its epitaph as a design rule: smart endpoints, dumb pipes. Intelligence lives at the ends, in services that own their domain and can be deployed alone. The stuff in between, gateways, buses, proxies, stays mechanical.

So where is the line for an edge that is supposed to compose things? Draw it between mechanical and meaningful. Terminating TLS, authenticating, limiting, routing, fanning out, reshaping fields, dropping a rail on timeout: mechanical. The edge may do all of it, because none of it involves knowing what the business means. Pricing, eligibility, what counts as "related", anything where the answer matters to the product: meaningful, and it belongs in a domain service, behind a contract, owned by the team that understands it.

The litmus test is almost embarrassingly practical: if a product manager can name the rule, it does not belong in the edge. A product manager will never ask about your TLS termination. They will absolutely ask why premium users saw the wrong discount, and when they do, you want that answer to live in one owned, versioned, testable service, not smeared across four BFFs and a gateway plugin.

Diagram titled: how much brain may the edge have? A thick horizontal line splits the picture. Below it, five solid green rungs the edge keeps: TLS and auth, limit and route, fan out and compose, reshape for the screen, drop a rail on timeout, labelled MECHANICAL and feeding a violet box that stays dumb pipes. Above the line, three dashed red rungs, what is related, eligibility and pricing, labelled MEANINGFUL, are pushed by arrows into an amber domain service box that is owned, versioned, testable and in one place. On the dividing line sits the litmus pill: PM can name it? it moves up. A red note recalls the history: the ESB died crossing it.

After the BFF: The Pendulum, and When Not to Bother

Zoom out far enough and the whole edge story is one pendulum, and no company shows the full swing better than Netflix.

They started where everyone starts: one general-purpose REST API, serving what became more than a thousand different device types. It could not fit them all, so around 2012 they rebuilt the edge around a blunt admission, and named the redesign after it: embracing the differences. Each UI team wrote its own client adapter scripts that ran on the API edge itself, so the TV team shaped TV payloads and the phone team shaped phone payloads, without waiting on the API team. That is BFF thinking, running as scripts inside one edge platform, fronted by their gateway Zuul at more than fifty thousand requests a second.

Then the pendulum kept moving. Per-client edges multiplied, iOS had an API, Android had an API, TV had an API, and the old bottleneck returned in a new costume: N parallel edges, each re-implementing access to the same domains. Netflix's current answer is a federated GraphQL edge: one graph that every client queries in its own shape, where each domain team owns and deploys its own slice of the schema, more than two hundred services federated behind a single consumer edge. Look at what survived every swing: client-shaped responses, and domain logic owned by domain teams. The only thing that changed is the machinery doing the composition.

That is also the honest frame for GraphQL as a BFF alternative in your own system: one endpoint, client-shaped queries, exactly as GraphQL: Ask for Exactly What You Need described, is the BFF idea expressed as a query language instead of N services. You trade N small deployables for one schema plus resolvers, and the costs move with the structure: resolver fan-out still needs budgets, caching gets harder than caching REST responses, and the schema becomes a shared surface that needs the same ownership discipline the graph gave Netflix.

And sometimes the answer is no edge specialisation at all. Skip the pattern when the divergence it exists to absorb is not there:

  • One client, or clients that genuinely make the same requests. A BFF for a single web app is an extra hop and an extra deployable in exchange for nothing.
  • Third parties. This one inverts completely. Your public API serves strangers you cannot co-release with, so it must be the opposite of a BFF: one general-purpose, stable, versioned contract, built with the discipline of API Design & REST: Contracts Between Strangers and Schema Evolution & API Versioning. SoundCloud's original API had to stay generic precisely because third parties depended on it. BFFs are for clients you own.
  • A team that cannot feed another fleet. Every BFF is a service: a deploy pipeline, an on-call rotation, a security surface. The pattern spends operational capacity to buy team autonomy, and that is a purchase you must be able to afford.

Takeaways

  • Whose shape does the API wear? A general-purpose API is noun-shaped; screens are scene-shaped. The gap is paid in round trips, discarded payload, and negotiation.
  • The general-purpose API's worst cost is organizational. It turns the API team into a queue every client team waits in.
  • A BFF is the client's server-side half. One per experience, owned by the client team, holding presentation and composition logic only.
  • The door duties stay at the door. One shared gateway keeps TLS, auth, and limits; BFFs sit behind it; domain services think below it. Three layers, three different owners.
  • Duplication moves down, not sideways. Tolerate duplicated glue; when it turns out to be domain logic, extract it into a service below, never a shared edge library.
  • Composition has a deadline. Fan out in parallel, give every call a budget from the client's patience, and decide before the bad day which parts of the screen are droppable. Partial response is a product feature, not an exception path.
  • Smart endpoints, dumb pipes. If a product manager can name the rule, it belongs in a service, not the edge. The ESB died teaching this.
  • The pendulum: generic, per-client, federated. What survives every swing is client-shaped responses and domain-owned logic.

Every concern in this lesson governed traffic walking in the front door, what the trade calls north-south traffic. But the same cross-cutting needs, auth, retries, encryption, observability, exist on every hop between services, east-west, where there is no door to put them behind. Moving those concerns out of your code and into the platform is the next pattern: Sidecar & Service Mesh.