Hermes Wiki
Developer/ArchitecturePatterns/Microservices/Fundamentals/backend-for-frontend-bff-pattern

Backend for Frontend (BFF) Pattern

Concept

A microservices system that grows past a handful of services creates a real problem for its client applications: a single screen — a mobile app's home feed, a web dashboard — often needs data assembled from five, ten, or more downstream services. If every client (web, iOS, Android, a partner integration) talks directly to all of those services and does its own aggregation, three things go wrong at once: each client duplicates the same aggregation logic in a different language/platform, every client is coupled to the internal shape of every service it calls, and a general-purpose "API Gateway" trying to serve every client's needs ends up either bloated with client-specific branches or forced into a lowest-common-denominator response shape that fits nobody well.

The Backend for Frontend pattern, coined by Sam Newman while at SoundCloud, resolves this by giving each client type its own dedicated backend service — a mobile BFF, a web BFF, a partner-API BFF — that sits between the client and the downstream microservices. Each BFF's job is narrow and specific: aggregate calls to whatever downstream services a given screen needs, reshape the combined result into exactly what that client consumes, and own client-specific concerns (payload size for mobile, browser session handling for web) without leaking those concerns into the shared domain services underneath. Crucially, a BFF is owned by the same team that owns the corresponding frontend — this is as much an organizational pattern as a technical one, removing the cross-team coordination tax of "the mobile team needs the backend team to change a shared gateway endpoint" by giving the mobile team its own service to change instead.

This distinguishes BFF from a single shared API Gateway: a gateway is one team's shared infrastructure serving every client from one codebase, while a BFF is per-client-type, deliberately not shared, and owned by the consuming team — the tradeoff is duplication across BFFs in exchange for zero cross-team coordination on client-specific changes.

Tradeoffs

Approach Client fit Team coupling Duplication cost
Clients call downstream services directly Poor — every client reimplements aggregation and absorbs every downstream service's shape High — any downstream service change risks breaking every client None, but the aggregation logic itself is duplicated in every client codebase
Single shared API Gateway for all clients Mediocre — one shape has to serve web, mobile, and partners at once Moderate — all client teams share one gateway team/codebase Low — one implementation, but full of client-specific conditional branches
One BFF per client type Strong — each BFF is shaped exactly for its client Low — each client team owns and changes its own BFF independently Higher — genuinely shared aggregation logic (e.g., "assemble a user profile") gets reimplemented per BFF unless deliberately factored into a shared library

The tradeoff is duplication versus coupling: a single shared gateway minimizes code duplication but maximizes cross-team coordination cost every time one client's needs diverge from another's; per-client BFFs eliminate that coordination cost but require discipline to avoid re-solving the same aggregation logic redundantly in every BFF, which is its own maintenance burden if left unmanaged.

When to use / when not to

  • Use BFFs once client types have genuinely divergent needs — a mobile app needing a lean, low-bandwidth payload and a web dashboard needing a rich, deeply nested one is the textbook signal.
  • Use BFFs when the team boundary matters as much as the technical one — if the mobile team is blocked on the backend team for every screen change, a dedicated BFF the mobile team owns directly removes that dependency.
  • Prefer a single API Gateway (no BFF split) when all clients genuinely need the same shape of data and the org is small enough that gateway changes aren't a coordination bottleneck — splitting into BFFs prematurely just adds services to operate for no real benefit yet.
  • Don't create a BFF per client without owning the resulting duplication — factor genuinely shared aggregation logic (assembling a user's identity, say) into a shared internal library or service that every BFF calls, rather than copy-pasting it into each one.
  • Don't let a BFF become a dumping ground for business logic that belongs in a domain service — its job is aggregation and reshaping for one client, not owning business rules that other consumers would also need.

Common pitfall

Letting a BFF slowly accumulate business logic that belongs in the underlying domain services, because it's the easiest place to add "just one more thing" for a specific screen. Once that happens, the BFF stops being a thin aggregation layer and becomes a second, undocumented copy of business rules that the actual domain service also implements (or should) — and when those two versions of the rule inevitably drift, no other client using that domain service gets the fix, and no other BFF that should apply the same rule gets it either. The pattern only stays healthy if the BFF stays disciplined as an aggregation-and-reshaping layer, not a second home for domain logic.

Engineering Lens

BFF is a useful lens for the broader Principal-level habit of treating "who owns this and who has to coordinate to change it" as an architectural decision, not just a technical one — Conway's Law made deliberate rather than accidental (see Team Topologies and Conway's Law). The strongest way to defend a BFF split in a design review isn't "our mobile payloads are different sizes" — it's naming the actual coordination cost being removed: which team currently has to wait on which other team, for what kind of change, and how often. That same reasoning applies directly to a Fintech or Capital Markets context: a retail trading app's BFF and an institutional API partner's BFF have wildly different latency, payload, and authentication requirements, and giving each its own dedicated layer — owned by the team that actually understands that client's needs — is a stronger architectural answer than forcing both through one general-purpose gateway trying to serve incompatible requirements at once.

Sources

Hermes Wiki