The BFF is the layer a frontend Lead actually owns. But "BFF vs GraphQL" is a false choice that trips up interviews — they operate at different levels. This lesson covers: what a BFF is, what REST and GraphQL BFFs each look like, when to reach for which, and how they connect to GraphQL Federation. The goal is a single, confident answer that holds up to pushback.
In a microservices world there is no single "backend" — there are dozens of small services (search, pricing, reviews, availability, loyalty…). If the browser talks to them directly, three pains appear, and they're the exact pains you've already met under other names:
A Backend for Frontend is a thin server-side layer that exists to serve one specific frontend experience. It sits between your UI and the microservices, calls them on the UI's behalf (over the fast internal network), and returns a single response shaped exactly for that screen. The pattern came out of SoundCloud — Phil Calçado and Sam Newman named it in 2015 — when one monolithic API couldn't serve a web UI and a mobile app whose needs sharply differed.
Two words carry the whole definition, and they're what separates it from a generic gateway:
It's coupled to a particular client on purpose. The mobile app's needs (tiny payloads, fewer round trips) differ from the web app's, so each gets its own BFF tuned to it. Coupling that would be a sin between services is the point here.
The team that builds the UI builds and owns its BFF. That's what gives the frontend team the autonomy to reshape its own API without filing a ticket against a backend team and waiting a sprint.
A BFF is one backend per frontend experience, owned by the frontend team. It exists to serve this UI — not to be a general-purpose API.
These get conflated constantly — distinguishing them cleanly is a quick credibility win. A BFF is a kind of API gateway, but a specialised one: one per client experience, rather than one shared edge for everybody.
| Approach | What it is | Best when |
|---|---|---|
| Direct to services | Browser calls each microservice itself | One simple client, few services. Waterfalls + coupling appear fast. |
| API Gateway | One shared entry point for all clients — global concerns: routing, rate-limit, authN, TLS | You need cross-cutting edge policy. But "one size fits all clients" can't optimise any single UI. |
| BFF | A dedicated backend per frontend, shaping responses for that UI | Multiple UIs with divergent needs (web vs mobile); the FE team wants to own its contract. |
They're not exclusive — a common production layout is a shared gateway for global concerns in front of per-client BFFs (Microsoft Azure Architecture Center).
HttpOnly cookie (ties directly to L18's XSS-vs-CSRF storage triangle).// One call from the browser → parallel fan-out server-side → one shaped response app.get('/bff/hotel-page/:id', async (req, res) => { const [hotel, price, reviews] = await Promise.allSettled([ // parallel, not waterfall getHotel(req.params.id), getPrice(req.params.id, req.user), // privileged creds stay here — never reach the browser getReviews(req.params.id), ]); res.json({ name: hotel.value.name, price: price.status === 'fulfilled' ? price.value : null, // degrade, don't fail rating: reviews.value?.avg, }); });
Now that you know what a BFF does, the question that trips up interviews: "Why BFF instead of GraphQL?" The correct answer is that this is a false choice — they operate at different levels:
A server-side aggregation layer, owned by the FE team. Nothing about the topology specifies how the browser talks to it.
REST: named HTTP endpoints (GET /bff/hotel-page/:id). GraphQL: one endpoint, flexible queries. The BFF does the same parallel fan-out behind either contract.
BFF is a topology; REST and GraphQL are contracts. A GraphQL server that aggregates microservices for one FE team is a BFF with a GraphQL interface — they're not alternatives, and you choose them independently.
The five BFF jobs don't change. What changes is how the browser expresses what it needs and how the contract is typed:
| REST BFF | GraphQL BFF | |
|---|---|---|
| Request | GET /bff/hotel-page/:id | query { hotel(id:$id){ name price } } |
| Field selection | Server decides — one fixed shape per endpoint | Client decides per query — zero over-fetch by design |
| New data need | New endpoint (or query params) | Add fields to the query; no new endpoint |
| HTTP caching | Native GET caching, CDN-friendly out of the box | Harder — POST by default; needs persisted queries |
| Type system | OpenAPI / manual contract | SDL introspection, auto-generated TS types |
| N+1 risk | Controlled at the endpoint level | Resolver-level — requires DataLoader / batching |
| Debugging | curl a URL in production | GraphiQL / Apollo Sandbox — richer but more setup |
| Learning curve | Low — standard HTTP | Higher — schema design, resolvers, DataLoader |
// REST BFF — fixed shape per screen, server decides fields app.get('/bff/hotel-page/:id', async (req, res) => { const [hotel, price, reviews] = await Promise.allSettled([ getHotel(req.params.id), getPrice(req.params.id, req.user), getReviews(req.params.id) ]); res.json({ name: hotel.value.name, price: price.status==='fulfilled' ? price.value : null, rating: reviews.value?.avg }); }); // GraphQL BFF — identical fan-out, but the client picks which fields it needs const resolvers = { Query: { hotel: async (_, { id }, { user }) => { const [hotel, price, reviews] = await Promise.allSettled([ getHotel(id), getPrice(id, user), getReviews(id) ]); return { name: hotel.value.name, price: price.value, rating: reviews.value?.avg }; } } }; // Detail page: query HotelPage($id:ID!){ hotel(id:$id){ name, price, rating } } // List card: query HotelCard($id:ID!){ hotel(id:$id){ name, price } } // Same BFF, same fan-out — client declares exactly what it needs per call
The question isn't "BFF or GraphQL" — it's which contract on top of your BFF topology, and when does the topology itself need to change?
curl-able URLs beat a query playground during an incident.With SSR and React Server Components (L09), the framework's server layer already plays the BFF role: a Next.js Route Handler, a server action, or an RSC fetching data on the server is doing exactly the BFF's five jobs — aggregating services, holding secrets, shaping the response, and owning degradation. RSC takes it furthest: the component fetches and renders on the server, so the data-shaping layer and the UI that consumes it are the same code, and zero aggregation JS reaches the browser.
This means: in a Next.js stack, you don't need a separate BFF tier for the web surface. You'd only add a dedicated BFF for a client that doesn't go through that server — a native app, a partner API, a CLI tool.
If you're doing SSR or RSC, you already have a BFF — your server layer. Add a dedicated BFF tier only for clients that don't run through that server.
A BFF is not free. The Lead-level answer names the downsides without being asked:
How to land all of this in the room in one confident answer:
"I'd put a BFF per surface — web and native have very different payload budgets. It collapses the hotel-page waterfall into one datacenter-side fan-out, trims responses to what each card needs, holds the auth tokens server-side so nothing sensitive reaches the browser, and owns graceful degradation when pricing flakes. In our Next.js stack the Route Handlers and Server Components are that BFF — no separate tier needed for web. I'd start with a REST contract: screen shapes are stable, GET endpoints are CDN-cacheable, and we ship fast. I'd switch to a GraphQL contract when field-selection proliferation kicks in. And if we hit BFF sprawl across many surfaces, that's the specific trigger for GraphQL Federation — keeping each domain team's subgraph independent. The key thing: BFF and GraphQL aren't alternatives; GraphQL is just a contract you put on top of the BFF topology."
Concept: a BFF is one thin, FE-owned API per surface — it collapses the client's request waterfall into a single datacenter-side fan-out, trims each response to what that surface needs, and holds auth tokens server-side; in a Next.js stack the Route Handlers + Server Components are that BFF. Trade-off: it's another tier to run and "BFF down = UI down," so you own its availability and keep it thin (orchestration only, no business logic) — and you pick the contract by pain: REST first (stable screen shapes, CDN-cacheable GETs, fast ship), GraphQL when field-selection proliferation bites, Federation only once you hit BFF sprawl. Anchor: "On the hotel page we replaced a 5-hop client waterfall (~800ms) with one BFF fan-out (~260ms) — 3× faster on WiFi, ~400–500ms saved on SEA 4G — by trimming payloads per card and holding tokens server-side; we stayed on REST until field proliferation pushed one surface to GraphQL." Impact: that latency converts directly into booking conversion, and tokens never reaching the browser shrinks the security surface. Invite: "I'd reach for GraphQL Federation only when many per-client BFFs start re-implementing the same composition — SoundCloud migrated exactly that way; before sprawl, a REST BFF is the simpler bet."
0 / 9 correct
1. the hotel detail page makes 5 sequential calls to 5 microservices from the browser; it's slow on SEA mobile. What does a BFF primarily fix here?
2. What's the key distinction between a BFF and a general API Gateway?
3. Why is "owned by the frontend team" considered the defining organizational property of a BFF?
4. A reviewer says "putting the auth token in the BFF instead of the browser is just moving the problem." Why is it actually a security win?
Tie this back to the L18 storage triangle.
5. You're already building the web app in Next.js with Server Components and Route Handlers. A teammate proposes adding a separate BFF tier. What's the sharpest response?
6. A new hotel-page BFF has grown to contain pricing rules, loyalty-tier logic, and tax calculation. Why is this a smell?
7. Your org now runs web, iOS, Android, and partner-widget BFFs that all re-implement the same hotel composition, and the count is climbing. What's the recognised next step?
8. An interviewer asks: "Why would you use a BFF instead of GraphQL?" What's the most accurate reframe of this question?
You suggested a BFF; they pushed back with GraphQL. This is the exact interview trap from §5.
9. Your REST BFF for the hotel page has grown to 30+ endpoints. Different page components need different subsets of the same hotel object — card needs name+price, detail needs name+price+reviews+amenities — and you keep adding endpoints. What's the specific trigger that justifies switching to a GraphQL BFF?
In 90 seconds: "Design the data layer for the hotel detail page across web and native." Cover: the waterfall problem, why a BFF per surface, the five jobs (aggregate / shape / orchestrate / secure / degrade), how it maps onto your SSR/RSC server, and — critically — when you'd use a REST vs GraphQL contract on that BFF, and what specific pain triggers each upgrade (GraphQL BFF → Federation).
Good follow-up topics: