Backend-for-Frontend vs API gateway
The gateway does what every request needs; a BFF does what one user experience needs, and that experience's team runs it.
Predict first
Before you read: predict
ExamHall's mobile team is building the app's exam home screen. It shows the exam's title and start time from the exam API, the number of answered questions from the attempts API and the proctor's check-in state from the proctoring API. Today the app calls all three APIs itself, over train and café connections, and shows a few fields of each reply. The web app's home screen shows other fields from the same APIs. The API gateway in front of the APIs already checks tokens and rate limits for every client.
Where should the home screen's combined reply be built?
The 30-second version
An API gateway is one shared front door: it checks tokens, applies limits and routes, the same for every client. A BFF is a backend owned by one frontend's team: it calls several services and returns what one screen shows. Keep screen shaping out of the gateway, and business rules out of BFFs.
The gateway does what every request needs; a BFF does what one user experience needs, and that experience's team runs it.
How it works
Step 1 of 12
Analogy: A trade fair at an exhibition centre
| At the trade fair | In the system |
|---|---|
| the venue's entrance hall checks every badge and closes the doors when the hall is full, for every exhibitor alike | the gateway's token checks, limits and routing (step 2) |
| each exhibitor's own staff fetch leaflets, samples and prices from their back office and hand a visitor one bag | a BFF owned by one frontend team, aggregating and shaping (steps 3–5) |
| the venue desk starts packing one exhibitor's bags, so every exhibitor queues at the venue office for each change | screens in the gateway (step 7) |
| a trade stand and a public stand each print the discount on their own sheets, and quote two prices after head office changes it | a rule copied into BFFs drifts (step 8) |
In real terms: put the work every request needs at the shared edge, and the work one screen needs in that screen's own backend.
Breaks down when: visitors can see the stand's staff at work. A BFF's calls happen out of the client's sight, inside the perimeter.
Contrasting case: a single shop with one door and one counter needs neither a venue desk nor separate stands.
Examples, run for real
4.1 ExamHall EXH-2700: three calls from the browser
EXH-2700: ExamHall's candidate app once loaded the exam home screen in the browser, calling the exam, attempts and proctoring APIs through the gateway, each reply a whole record. MR !3000 moved those calls into the app's Next.js server layer, which calls the same APIs on the server and sends the page only the screen's fields. Here a node:http stand-in plays that server layer:
4.1 EXH-2700 browser calls each API: requests over the candidate's connection 3 · bytes 1248 · API calls 3
4.1 MR !3000 server layer as BFF: requests over the candidate's connection 1 · bytes 109 · API calls 3
The APIs see the same three calls; the candidate's connection carries one (steps 4–5).
4.2 Proof domain, chat: two BFFs over one messages service
A chat company's web BFF and phone BFF read the same conversation from one messages service and look sender names up in one batch call:
4.2 web BFF: messages 40 · fields per message 6 · bytes 5517 · messages-service calls 1 · user-lookup calls 1 (7 senders)
4.2 phone BFF: messages 20 · fields per message 3 · bytes 1772 · messages-service calls 1 · user-lookup calls 1 (7 senders)
Each screen gets its own shape from the same service (step 5).
Picture it

Try it
Answer first, then check. Nothing is saved.
Question 1 of 6
A chat company allows each user 60 API requests a minute. It runs an API gateway that checks tokens and routes. Users reach its API three ways: the web app calls the web BFF, the phone app calls the phone BFF, and bots call the bot API. The two BFFs read and post messages through the messages service; the bot API runs bots' commands in the commands service. Today the web BFF and the phone BFF each count each user's requests and refuse any past 60 in a minute; the bot API counts nothing. In a test minute, user u-7 sends 50 requests from the web app, 50 from the phone app and 50 from a bot, taking turns.
Which one change admits u-7 at most 60 times in that minute, while a user who sends 60 from the phone app alone still has all 60 admitted?
Question 2 of 6
A chat app lets a sender edit a message for 15 minutes after sending it. The web BFF and the phone BFF each check that window before they pass an edit to the messages service, which stores every edit it receives. The desktop app's team starts its own BFF next sprint and plans to copy the check into it.
What should the teams do with the check?
Question 3 of 6
MR !3001 in ExamHall's candidate app (Next.js 16). The exam home page is a Server Component rendered on each request:
app/api/exam-home/[id]/route.ts, a Route Handler in the same app, calls the NestJS exam and proctoring APIs and merges their replies.
What should the review ask for?
// app/exam/[id]/page.tsxexport default async function ExamHome({ params }: { params: Promise<{ id: string }> }) { const { id } = await params; const res = await fetch(`${process.env.APP_ORIGIN}/api/exam-home/${id}`, { cache: 'no-store' }); return <Home data={await res.json()} />;}Question 4 of 6
A payments company's API gateway holds a response template for each screen of its web dashboard, its phone app and its partner portal; each template calls the ledger services and reshapes their replies. A platform team of four owns the gateway and releases it every two weeks. Template changes from the three UI teams now wait 11 days on average before they ship.
What should the architecture review recommend?
Question 5 of 6
A ticketing company plans its backends. Its iOS and Android apps show the same screens and are built by one mobile team; its box-office kiosk prints tickets and takes cash, and is built by the venue-systems team. The plan's notes:
- Phone apps: the iOS and Android apps share one BFF, since their screens match.
- Kiosk: it calls the phone apps' BFF too, since all three apps sell the same tickets.
- Ownership: the mobile team builds and runs the phone apps' BFF, since it changes with their screens.
- Calls: the phone apps' BFF fetches the event first, then calls the seat service and the price service at the same time, since the screen waits for both.
Which note should the review push back on?
Question 6 of 6
A streaming service keeps titles rated 18 away from kids profiles. Its gateway config, its two BFFs' play handlers, its playback service's handler and its nightly preview job are below:
# gateway.yaml
auth: verify-token
rate_limit: 60/min per user
routes:
/web/*: web-bff
/tv/*: tv-bff
/support/*: playback
This evening four requests ask to play t-18, rated 18, for the kids profile p-kid: one from the web app, one from the TV app, one from a support agent's tool, which calls /support/play, and the preview job's.
How many sessions does the playback service start?
// web-bff, POST /web/play (profile and title are loaded from the request's ids)if (profile.kids && title.rating === '18') return reply(403);return reply(201, await forward('playback', { profileId: profile.id, titleId: title.id })); // tv-bff, POST /tv/play (profile and title are loaded from the request's ids)return reply(201, await forward('playback', { profileId: profile.id, titleId: title.id })); // playback, POST /playconst session = await db.sessions.insert({ profileId: body.profileId, titleId: body.titleId });return reply(201, session); // preview-job, run nightly inside the networkawait post('http://playback.internal/play', { profileId: 'p-kid', titleId: 't-18' });Go deeper
Pro tips
- Give each BFF route a test that counts its downstream calls per request and fails when the count grows with the list it returns.
When: a screen's BFF looks a related record up for each item in a list (step 4; the N+1 shape belongs to
db-n-plus-one). Verify (RAN,tips.mjs):tip 1 phone BFF, one user lookup per message: user-lookup calls 20 for 20 messages tip 1 phone BFF, one batch lookup: user-lookup calls 1 for 20 messages
Source
The Next.js pages and the IETF draft change between releases, so their claims are labelled as of 2026-10-03.
- Sam Newman, Pattern: Backends For Frontends (18 November 2015), the row's first source: https://samnewman.io/patterns/architectural/bff/ (
newman-bff.txt). samnewman.io refused connections from this machine on 2026-10-03, and again on the R1 retry (curl exit 28, 2026-10-03T21:30Z), so the page was read from the Internet Archive's unmodified capture of that URL, taken 2026-09-13: https://web.archive.org/web/20260913115707id_/https://samnewman.io/patterns/architectural/bff/. The file's header records the capture (Memento-Datetime: 2026-09-13T11:57:07Z) and the raw file's sha256, and every Newman passage this lesson quotes is also verbatim in an independent capture of 2024-01-02. - Teaching only (step 6); it keys no item.
- Next.js docs, How to use Next.js as a backend for your frontend (last updated 25 June 2026): https://nextjs.org/docs/app/guides/backend-for-frontend (
next-bff-guide.txt), as of 2026-10-03. - Next.js docs, proxy.js file convention: https://nextjs.org/docs/app/api-reference/file-conventions/proxy (
next-proxy.txt), as of 2026-10-03. - Next.js blog, Next.js 16: https://nextjs.org/blog/next-16 (
next-16-blog.txt), the row's fourth source: "proxy.ts replaces middleware.ts and makes the app’s network boundary explicit." Teaching only (step 12). - Next.js docs, route.js file convention: https://nextjs.org/docs/app/api-reference/file-conventions/route (
next-route.txt), read for the Route Handler API; it keys nothing.
Related lessons
- Read first: Choosing an API style, REST vs GraphQL vs tRPC vs gRPC
- Read first: Browser credential storage boundaries
- Read first: Contract evolution: breaking changes, spec diffs, versioning and deprecation
- Related: OAuth 2.0 authorization code with PKCE, and what OIDC adds
- Related: Rate limiting
- Related: API contract from the client's side
- Related: Retries with exponential backoff and jitter
- Related: Modular monolith vs microservices
Checked against the tools and docs as of 2026-10-03.