The Backend for Frontend (BFF) Pattern

When designing application architectures, it is common to start by routing all user interface traffic through a single, all-purpose backend entry point. However, as you introduce a diversity of clients—such as Desktop Web, iOS, Android, or external integrations—this single, general-purpose backend inevitably becomes a delivery bottleneck.
Because one backend is forced to serve completely different user experiences, it usually requires a dedicated core backend team to mediate conflicting requirements. When a specific frontend team needs to rapidly iterate, they end up stuck in a cross-team negotiation loop, waiting on API changes that might conflict with another client's needs.
The Backend for Frontend (BFF) pattern is an organizational solution to this friction. It shifts ownership by creating small, specialized edge services that are maintained by the exact same teams building the specific user interfaces, giving them the autonomy to adapt APIs as quickly as their UI requires.
A common point of confusion is how a BFF fits alongside existing networking layers. In a production environment, a BFF does not replace your primary api gateway; instead, it works immediately behind it.
The traffic flows progressively from the user down to your core data services:
[ Client Application / 3rd Party ] │ ▼ [ Central API Gateway ] <-- Global Perimeter (Routing, DDoS, Rate Limiting) │ ▼ [ Backend for Frontend (BFF) ] <-- Experience Layer (Owned by UI Team) │ ▼ [ Downstream Microservices ] <-- Core Business Logic (Users, Products, Orders)
While both components operate at the edge of your infrastructure, they serve completely different operational and organizational purposes.
| Feature | Central API Gateway | Backend for Frontend (BFF) |
|---|---|---|
| Primary Scope | Global infrastructure/routing across the whole company. | Application logic for a specific client experience. |
| Ownership | Platform, DevOps, or Core Backend teams. | Frontend / Product teams building the specific UI. |
| Main Job | Reverse proxy, global rate limiting, DDoS mitigation. | API orchestration, payload trimming, error fallback. |
| Quantity | Typically one centralized instance per environment. | One per unique team boundary or consumer experience. |
A common oversimplification is that you must always have a strict 1:1 mapping between a client operating system and a BFF (e.g., one for iOS, one for Android). In practice, defining these boundaries requires balancing the distinct needs of the user experience with the realities of team ownership.
If your iOS and Android applications share a highly unified user experience and are built by a single, cross-functional mobile team, sharing a single "Mobile BFF" can be an operationally efficient choice.
However, this choice carries an organizational trade-off. If that team eventually splits into dedicated iOS and Android specialists, a shared BFF quickly becomes a cross-team deployment bottleneck—the very problem the pattern was meant to solve. Aligning the BFF's scope to a single, independent client application ensures that future team splits don't turn the BFF into a shared monolith.
BFFs are not restricted to internal user interfaces; they are equally valuable for managing external third-party integrations (e.g., public ecosystem APIs, partner webhooks, or legacy distributors).
Because you cannot force third parties to update their client code on your schedule, their integration requirements are inherently fixed. Creating a dedicated Third-Party BFF isolates these legacy consumer needs, ensuring you don't have to version-lock your internal core microservices just to accommodate an external consumer who cannot upgrade.
Once the boundaries are defined, a practical question arises: Where should this code actually live in Git? Two approaches tend to work well depending on your team's workflow.
The frontend application and its corresponding BFF live inside the same Git repository, managed as distinct packages or workspaces.
The frontend application lives in one repository, while the BFF lives in its own standalone backend repository.
Regardless of your repository strategy, it is best practice to treat the BFF as an independent deployable unit. Running the BFF on lightweight container setups or serverless runtimes allows it to scale dynamically alongside client traffic without impacting downstream microservices.
If your team uses a standard GraphQL setup, do you still need a dedicated BFF?
The answer often depends on what your frontend requires. A basic GraphQL server naturally resolves the problem of data aggregation, allowing the frontend to request precise fields from multiple sources in a single query. For many projects, introducing GraphQL effectively fulfills the data-shaping duties of a REST-based BFF.
However, a BFF handles responsibilities beyond raw JSON data fetching. For instance, managing client-specific authentication states, handling security token handshakes, or orchestrating specialized protocol translations are operational tasks that a data graph alone is not fundamentally designed to solve. When these requirements arise, a lightweight BFF layer wrapping or sitting alongside your graph provides a clear separation of concerns.
To see how this separation of concerns operates in production, let's look at three practical scenarios ranging from basic data handling to real-time stream mediation.
Imagine a typical product detail page. To display this screen, the system needs to fetch data from three places: the product catalog, inventory tracking, and customer reviews.
If the customer reviews service suddenly goes offline, a generic API gateway might bubble up a 500 Internal Server Error, causing the entire product page to crash for the end user.
A BFF provides a clean place to catch that error. It can log the failure, hide the broken module, and return the core product details with an empty array for reviews. The customer can still buy the item, preserving the core business transaction.
A core backend microservice might return a vast user profile dataset containing hundreds of historical fields, internal system IDs, and nested configuration records.
If a mobile application only needs to display the user's first name and a thumbnail photo on a small top navigation bar, forcing a phone on a weak cellular connection to download that entire payload is inefficient. The BFF can intercept that massive object, pluck out just the two required fields, and pass a lightweight, highly optimized JSON response to the device.
Modern frontends frequently handle live, streaming data—such as real-time financial tickers, live sports scores, or streaming text responses from an AI/LLM service.
Your internal core microservices might communicate using heavy, persistent protocol streams (like gRPC or internal message queues). Connecting clients directly to these internal streams is rarely practical, but for different reasons:
A BFF acts as a vital protocol mediator here. It maintains the heavy, high-throughput connections to your internal microservices within the data center. It then translates and flattens that data into a single, web-friendly Server-Sent Events (SSE) stream or a lightweight WebSocket connection that both web browsers and mobile apps can easily consume without lagging the UI or draining the battery.
For web applications, deciding where to manage security tokens can be challenging. Storing sensitive access and refresh tokens directly in browser storage (like localStorage) exposes them to data theft if a malicious script manages to run via a Cross-Site Scripting (XSS) vulnerability.
To address this risk, security working groups recommend utilizing the Token Handler Pattern within your BFF architecture.
( Subsequent requests use secure cookie ) ┌───────────────────────────┐ ▼ │ [ Browser SPA ] ──────────( Secure Cookie )──────────► [ BFF Server ] ──( Unencrypted Token )──► [ Microservices ]
To maximize protection, this cookie is locked down using strict browser directives:
HttpOnly to ensure client-side JavaScript cannot read or access the cookie contents.Secure to guarantee the cookie is only transmitted over encrypted HTTPS connections.SameSite=Strict or Lax to protect against Cross-Site Request Forgery (CSRF) attempts.__Host- prefixes to bind the cookie strictly to your primary domain name, preventing subdomain interception.No architecture pattern comes without a cost, and evaluating a BFF requires balancing its organizational agility against its real-world operational friction points.
When implementing the Token Handler pattern on the web, how you choose to map that secure browser cookie back to the unencrypted token changes how your BFF scales:
The Backend for Frontend (BFF) pattern is ultimately less about introducing new infrastructure and more about shifting ownership. By placing the data-shaping and orchestration layer directly into the hands of the frontend teams, you eliminate the classic organizational bottleneck of waiting on core backend teams to modify upstream APIs for simple UI updates.
However, as with any architectural shift, the BFF is not a silver bullet. Choosing this path requires a pragmatic look at your team's operational maturity:
When to lean in:
When to pause: If your application is primarily a single web interface with straightforward data needs, adding a BFF might introduce unnecessary network latency and operational overhead.
As always, I may miss some points, so if you have any questions or suggestions, please feel free to share them with me. If you know a better approach, I’d love to hear about it. I’m always open to learning and improving my knowledge.
Stay humble and keep learning!