Filter concepts by levelShowing all levels.

System Design · API Gateway and BFF

API Gateway and BFF

One shared entry point vs one backend per client, what a gateway actually does to a request, and how it quietly becomes a monolith

  • API Gatewayone contract, every client
  • BFFone backend, one client experience
  • Aggregate + shapefan out, merge, trim per client
  • Logic creepthe untestable-monolith anti-pattern

1.API Gateway vs Backend-for-Frontend

An API gateway gives every client — web, mobile, third-party — the same shared entry point and the same response shape. A Backend-for-Frontend (BFF) is a separate backend layer per client type, each one shaped around what that specific client actually needs.

Key features

  • An API gateway serves one shared contract to all clients — web, iOS, Android and third parties all call the same endpoints and get the same response shape
  • A BFF is one backend per client experience (a web BFF, an iOS BFF, an Android BFF) — each one calls the same downstream services but returns a shape tailored to that client
  • A BFF is typically owned by the same team that owns the client it serves, not by a central platform team
  • A shared gateway forces every client to negotiate a common response shape, which tends toward a lowest common denominator or a bloated payload with fields only some clients need
  • A BFF adds an extra deployable per client type — more services to run, version and keep available

Choosing between a shared API gateway and one BFF per client

Choosing between a shared API gateway and one BFF per client
SituationBetter fit
One client type, or all clients need near-identical dataShared API gateway
Mobile needs a lean payload, web needs a rich oneBFF per client
Small team, limited operational budgetShared API gateway — fewer services to run
Each client team ships and iterates independentlyBFF per client — no shared-contract negotiation
Third-party or public API consumersShared API gateway — a stable, general contract
One shared contract vs one backend per client

API Gateway

  • One entry point, one response shape, for every client
  • Owned by a central platform team
  • Simple to run, but the contract must satisfy every client at once

Backend-for-Frontend

  • One backend per client experience (web, iOS, Android)
  • Owned by the team that owns the matching client
  • More services to run, but each shape fits its one client exactly
  • API Gateway
    • One entry point, one response shape, for every client
    • Owned by a central platform team
    • Simple to run, but the contract must satisfy every client at once
  • Backend-for-Frontend
    • One backend per client experience (web, iOS, Android)
    • Owned by the team that owns the matching client
    • More services to run, but each shape fits its one client exactly

Command

text
API gateway:  web ─┐
              mobile ─┼─→ [one gateway, one contract] → services
              partner ┘

BFF:          web ────→ [web-bff]    ─┐
              mobile ──→ [mobile-bff] ─┼─→ services
              partner ─→ [partner-bff]┘

An API gateway is one shared entry point and contract for every client; a BFF is one backend per client experience, shaped around what that one client actually needs. Reach for a BFF only when clients genuinely diverge — otherwise it is extra services with nothing to show for them.

2.Aggregation, authentication, throttling and response shaping

Aggregation fans a single client request out to several backend services and combines the results into one response. Authentication verifies identity once, at the edge, instead of in every service. Throttling caps how many requests a client can make. Response shaping trims or reshapes a payload for the client that asked for it.

Key features

  • Aggregation: the gateway or BFF calls multiple backend services for one incoming request and merges their responses before replying — the client makes one round trip instead of several
  • Aggregation calls are typically issued in parallel, not sequentially, when the backend calls do not depend on each other — sequential fan-out adds each service's latency instead of taking the slowest one
  • Authentication centralizes identity verification at the edge — a token is checked once, and downstream services trust an already-verified identity instead of re-validating it
  • Throttling caps requests per client per time window (the roadmap's word for rate limiting) — it protects backend services from a single client, buggy or malicious, consuming disproportionate capacity
  • Response shaping reshapes or trims a payload for the client asking for it — a BFF's core job, and also something a shared gateway can do per-route but not per-client without branching

The four mechanics and where each one actually runs

The four mechanics and where each one actually runs
MechanicWhat it doesFailure mode if skipped
AggregationFans out to N services, merges N responses into 1Client makes N round trips itself, paying full latency N times
AuthenticationVerifies identity once at the edgeEvery service re-implements and re-drifts its own auth check
ThrottlingCaps requests per client per windowOne noisy client can exhaust capacity meant for everyone
Response shapingTrims/reshapes payload per clientEvery client receives the same bloated, one-size-fits-all body
One request: authenticate, throttle, fan out, shape, fan in

Client

one request

BFF

auth, throttle, aggregate, shape

profile-service

feed-service

notifications-service

  • Client — one request
    • leads to BFF
  • BFF — auth, throttle, aggregate, shape
    • leads to profile-service
    • leads to feed-service
    • leads to notifications-service
  • profile-service
  • feed-service
  • notifications-service

Command

text
handle(request):
  identity = authenticate(request.token)          # once
  if not withinLimit(identity, window): return 429 # throttle
  results = parallel(                              # aggregate
    call(serviceA), call(serviceB), call(serviceC))
  return shape(results, request.clientType)         # shape

Order matters: authenticate and throttle first — they are cheap and can reject outright — then aggregate (in parallel, when calls do not depend on each other), then shape the combined result for the specific client asking.

3.The gateway that became an untestable monolith

A gateway or BFF starts as a thin routing layer, but every team finds it convenient to add "just one more thing" — a retry policy, a per-client conditional, a cached response, a feature flag. None of these alone looks like a monolith. Together, over enough time, they turn the one service every request must pass through into a single codebase no team can change without risking every other team's traffic.

Key features

  • Logic creep is not one bad decision — it is many individually-reasonable additions (a retry policy here, a per-client branch there, a cached response, a feature flag) that nobody steps back to look at as a whole
  • A monolithic gateway becomes a single point of failure for every team's traffic — one bad deploy to the gateway can break every client at once, not just the feature that changed
  • It also becomes an organizational bottleneck: any team that needs a gateway change must coordinate with whoever owns the gateway, instead of shipping independently
  • "Untestable" specifically means the gateway's combined behavior (every route, every conditional, every integration) has grown too large for one team to write tests that cover it, so regressions are caught in production instead of before deploy
  • The gateway growing large is not itself the problem — accreting logic that belongs to individual services, with no owner tracking the total, is what turns size into risk

Individually reasonable additions that accumulate into an untestable monolith

Individually reasonable additions that accumulate into an untestable monolith
AdditionWhy it seemed fine aloneWhat it costs once dozens exist
A retry policy for one flaky downstream callFixes a real, narrow problem quicklyDozens of undocumented retry policies, each slightly different, none owned
A per-client conditional (`if clientType === "ios-legacy"`)Unblocks one client team without a new deploy elsewhereA growing if/else tree nobody can trace end to end
Response caching for one expensive callImproves latency for a real hotspotCache invalidation rules scattered across routes with no shared policy
A feature flag check for one experimentLets one team ship a trial without touching servicesFlags outlive their experiments and nobody is sure which ones still matter
A one-off transformation for one client's payloadSmall, contained, "just for now"Dozens of one-off transforms, each a hidden dependency a service does not know exists
Every team's traffic funnels through one increasingly overloaded gateway

Web team

Mobile team

Partner team

Gateway

routing + retries + conditionals + caching + flags

Backend services

every team still depends on the gateway deploying cleanly

  • Web team
    • leads to Gateway
  • Mobile team
    • leads to Gateway
  • Partner team
    • leads to Gateway
  • Gateway — routing + retries + conditionals + caching + flags
    • leads to Backend services
  • Backend services — every team still depends on the gateway deploying cleanly

Command

text
Healthy gateway growth:     routes -> more routes
                             (still just routing)

Monolith drift:              routes -> routes + retries
                                     + conditionals
                                     + caching rules
                                     + feature flags
                                     + one-off transforms
                             (routing, and everything else)

A gateway earns its keep by staying a thin, well-tested router plus a small fixed set of cross-cutting concerns. Every retry policy, per-client conditional, caching rule or one-off transform added "just this once" is a small loan against that; enough of them and the gateway becomes a single point of failure and a bottleneck every team depends on but no team can safely change alone.

Remember

  • An API gateway gives every client one shared contract; a BFF gives each client experience its own tailored backend.
  • Authenticate and throttle first, aggregate in parallel, shape the response last — order determines what gets wasted on a request that was always going to be rejected.
  • A gateway earns its keep as a thin router plus a fixed set of cross-cutting concerns — every "just add it here" retry policy or conditional is a loan against that.
  • Logic creep in a gateway is cumulative and individually invisible — the risk only becomes visible once nobody can test the whole thing anymore.

Pro tip

  • Reach for a BFF only when clients genuinely diverge in shape or cadence — otherwise it is extra deployables with nothing to show for them.
  • Push retry policies, caching rules and one-off transforms back into the owning service or BFF on an ongoing basis, not as a one-time cleanup.

Next up

  • API idempotency
  • Authentication and authorization
  • Security architecture

Sources

  • Sam Newman — Backends For Frontends pattern — samnewman.io/patterns/architectural/bff
  • Checked 2026-08-25

Spotted a mistake on this page? Tell us.