Backend for Frontend: Tailoring APIs to Each Client
A mobile app and a desktop web app have very different needs. The mobile client wants minimal payloads, prefers fewer round-trips, and cares about battery-efficient data fetching. The web client can handle richer responses and parallel requests. When both clients share a single general-purpose API, neither gets an optimal experience. The Backend for Frontend (BFF) pattern solves this by giving each client type its own dedicated backend.
The Problem with One API for All#
A shared API must serve the lowest common denominator. This leads to predictable problems:
- Over-fetching — The mobile client receives fields it never renders because the web client needs them.
- Under-fetching — The web client makes multiple calls to assemble a single view because the API was designed around mobile's simpler screens.
- Coupled release cycles — A change needed by the mobile team risks breaking the web client. Both teams coordinate releases around a shared contract.
- Bloated endpoints — Query parameters and feature flags accumulate to support every client's needs. The API becomes hard to maintain.
What Is a BFF?#
A BFF is a thin backend service that sits between a specific client and the downstream microservices. Each client type gets its own BFF:
┌──────────┐ ┌──────────────┐ ┌─────────────────┐
│ Web App │────►│ Web BFF │────►│ │
└──────────┘ └──────────────┘ │ Downstream │
│ Microservices │
┌──────────┐ ┌──────────────┐ │ (Orders, Users,│
│ Mobile │────►│ Mobile BFF │────►│ Products...) │
└──────────┘ └──────────────┘ │ │
│ │
┌──────────┐ ┌──────────────┐ │ │
│ Smart TV │────►│ TV BFF │────►│ │
└──────────┘ └──────────────┘ └─────────────────┘
Each BFF is owned by the frontend team that consumes it. The web team controls the web BFF. The mobile team controls the mobile BFF. This eliminates cross-team coordination for API changes.
What the BFF Does#
Response Shaping#
The BFF aggregates data from multiple downstream services and shapes the response for its client. A product detail page on web might need product info, reviews, recommendations, and seller data. The web BFF makes four parallel calls and returns a single merged response.
The mobile BFF for the same page might skip recommendations and return a compressed image URL instead of the full-resolution one.
Protocol Translation#
The downstream services might use gRPC, message queues, or event streams. The BFF translates these into the protocol the client expects — typically REST or GraphQL over HTTP.
Authentication and Session Management#
Each client type may have different auth flows. The web BFF handles cookie-based sessions. The mobile BFF handles token refresh and biometric auth flows. Keeping this logic in the BFF avoids polluting downstream services with client-specific auth concerns.
Caching#
The BFF can cache responses tailored to its client's access patterns. The TV BFF might aggressively cache the home screen layout since it changes infrequently. The web BFF might cache less aggressively because the web app shows real-time data.
One BFF Per Client Type#
The key design decision is granularity. The recommended starting point is one BFF per client platform:
- Web BFF — Serves the browser-based SPA or server-rendered app.
- Mobile BFF — Serves iOS and Android. If the two platforms diverge significantly, split into separate BFFs.
- TV / Kiosk BFF — Serves constrained devices with limited rendering capabilities.
- Third-party / Public API — If you expose a public API, it acts as its own BFF with stability guarantees, rate limiting, and versioning.
Do not create a BFF per page or per feature. That leads to an unmanageable number of services. The BFF should map to a deployment unit that the frontend team can own end to end.
GraphQL as a BFF#
GraphQL naturally serves as a BFF. The client declares exactly what data it needs, and the GraphQL server resolves it from downstream services. This eliminates over-fetching and under-fetching by design.
┌──────────┐ ┌──────────────────┐ ┌─────────────────┐
│ Client │────►│ GraphQL BFF │────►│ Microservices │
│ │ │ (resolvers map │ │ │
│ │◄────│ to services) │◄────│ │
└──────────┘ └──────────────────┘ └─────────────────┘
When GraphQL Works as BFF#
- Multiple clients with different data needs query the same backend.
- The frontend team wants to iterate on data requirements without backend changes.
- The downstream data model is relatively stable.
When GraphQL Falls Short#
- You need client-specific business logic beyond data shaping (e.g., different auth flows, different caching strategies).
- The GraphQL schema becomes a monolith shared across teams, recreating the original shared API problem.
- Performance-critical paths need hand-optimised responses that a generic resolver cannot produce.
In practice, many teams use GraphQL within a BFF rather than as the BFF. The BFF handles client-specific concerns, and GraphQL handles data fetching flexibility.
BFF vs Shared API Gateway#
An API gateway and a BFF serve different purposes:
| Aspect | API Gateway | BFF |
|---|---|---|
| Purpose | Cross-cutting concerns (auth, rate limiting, routing) | Client-specific data aggregation and shaping |
| Ownership | Platform / infrastructure team | Frontend team |
| Scope | All clients | One client type |
| Logic | Minimal business logic | Client-specific business logic |
| Count | One (or one per environment) | One per client type |
These are complementary, not competing. A common architecture uses an API gateway in front of multiple BFFs:
Clients ──► API Gateway ──► BFFs ──► Microservices
The gateway handles TLS termination, rate limiting, and routing. The BFF handles aggregation, response shaping, and client-specific logic.
Deployment Strategies#
Separate Services#
Each BFF is an independently deployed service with its own CI/CD pipeline, scaling policy, and monitoring. This is the standard approach and gives maximum autonomy to each frontend team.
Serverless Functions#
For lightweight BFFs, serverless functions (AWS Lambda, Cloudflare Workers) reduce operational overhead. The BFF is a set of functions that aggregate and shape data. This works well when traffic is bursty and the BFF logic is thin.
Edge Deployment#
Deploy the BFF at the edge (Cloudflare Workers, Fastly Compute) to reduce latency. The BFF runs close to the user, caches aggressively, and fans out to origin services. Best for read-heavy workloads where the BFF primarily caches and reshapes data.
Co-located with Frontend#
In frameworks like Next.js or Remix, the BFF logic lives in server-side route handlers or API routes within the frontend project itself. This is the simplest deployment model — one repo, one deploy. It works until the BFF logic becomes complex enough to warrant its own service.
When NOT to Use BFF#
- Single client type — If you only have a web app, a BFF adds a network hop with no benefit. Use a well-designed API directly.
- Thin clients with similar needs — If web and mobile consume nearly identical data, a shared API with sparse fieldsets (or GraphQL) may suffice.
- Small team — Maintaining multiple BFFs requires multiple deployment pipelines. For a team of five, the operational overhead may outweigh the benefits.
Anti-Patterns#
- Shared BFF — A BFF that serves multiple client types defeats the purpose. You are back to the shared API problem.
- Business logic in the BFF — The BFF should aggregate and shape, not implement domain logic. Domain logic belongs in downstream services.
- BFF as a dumping ground — Resist the temptation to put auth, rate limiting, logging, and tracing in the BFF. Those belong in the API gateway or shared infrastructure.
- Too many BFFs — One per page, one per feature, one per team. This creates a distributed monolith of BFFs that is worse than the original shared API.
Key Takeaways#
- The BFF pattern gives each client type a dedicated backend owned by its frontend team.
- BFFs handle response shaping, aggregation, protocol translation, and client-specific caching.
- GraphQL can serve as a BFF or operate within one — choose based on whether you need client-specific logic beyond data shaping.
- Use an API gateway alongside BFFs, not instead of them.
- Start with one BFF per client platform and split further only when divergence justifies it.
The BFF pattern is not about adding more services for the sake of it. It is about aligning backend boundaries with frontend ownership so each team can move fast without breaking the other.
This is article #224 in the Codelit engineering series. Want to sharpen your system design and backend skills? Explore more at codelit.io.
Try it on Codelit
GitHub Integration
Paste any repo URL to generate an interactive architecture diagram from real code
Related articles
Try these templates
Scalable SaaS Application
Modern SaaS with microservices, event-driven processing, and multi-tenant architecture.
10 componentsMultiplayer Game Backend
Real-time multiplayer game server with matchmaking, state sync, leaderboards, and anti-cheat.
8 componentsAPI Gateway Platform
Kong/AWS API Gateway-like platform with routing, auth, rate limiting, transformation, and developer portal.
8 componentsBuild this architecture
Generate an interactive architecture for Backend for Frontend in seconds.
Try it in Codelit →
Comments