5 Best Practices for Backends-for-Frontends


Prithwish Nath
TL;DR
Backend for Frontend (BFF) is a pattern where each client type, such as web or mobile, gets a dedicated API layer optimized for its needs. This reduces over-fetching, simplifies client logic, and decouples frontend and backend release cycles. For teams scaling across many services, GraphQL Federation extends these benefits by providing a shared, unified API layer with stronger governance and composition workflows. WunderGraph's current product is Cosmo , an open-source GraphQL Federation platform — referenced in Practice 2 and the section below.
The Backend for Frontend (BFF) pattern remains a useful way to tailor APIs to specific client experiences, especially when web, mobile, and other interfaces have different data needs. Many teams later discover that as the number of services, teams, and client experiences grows, they need a more systematic way to compose and govern those APIs across the organization. That is where GraphQL Federation increasingly becomes the next step.
State of GraphQL Federation 2026
How are teams governing schema changes, handling production traffic, and measuring Federation success? Share your experience and get early access to the full report. For every valid survey completed, we'll donate $30 to UNICEF .
In many organizations, BFFs begin as a practical way to tailor APIs to different client experiences. Over time, though, teams often end up duplicating orchestration logic, ownership boundaries become blurry, and each BFF becomes another surface that has to be maintained, monitored, and evolved.
GraphQL Federation addresses that scaling problem differently. Instead of creating separate custom aggregation layers for each client, teams contribute domain-owned services to a shared graph, giving frontend teams one unified API while preserving backend ownership and clearer schema governance.
This does not mean BFFs disappear entirely. A BFF can still make sense for client-specific policy, presentation shaping, or edge concerns. But when multiple BFFs start solving the same cross-service composition problem, federation is often the cleaner long-term model.
Teams have made this shift in practice as well. SoundCloud, which helped popularize the BFF pattern, now uses WunderGraph Cosmo for GraphQL Federation; WunderGraph reports an 86% reduction in compute usage and a 45% improvement in one key query's latency after migration. Luxury Presence says it outgrew its BFF layers, adopted federation with Cosmo, reduced code reviews from 3 PRs per feature to 1–2, and now deploys up to 40 times a day.
For the service mesh, serverless, auth, and observability tooling that supports BFF implementations, see 5 Tools for Building Backends-for-Frontends.
Photo by Arnold Francisca on Unsplash
The Backends-for-Frontends (BFF) pattern is an interesting solution to a problem many teams face — meaningfully decoupling the frontend from the backend, insulating the former from changes to the latter.
In this article, I’m going to quickly explain the BFF pattern, and then focus on some of the best practices you should be following when implementing it. If you’d like to know more about the pattern itself, I’d recommend reading Sam Newman’s BFF pattern post . For lessons learned the hard way, see 7 key lessons from building production BFFs.
So let’s get going!
Teams usually adopt a BFF because frontend and backend development start stepping on each other. Without an interface-specific API layer, even small frontend changes can force backend changes, while backend evolution can break client experiences or push business logic into the UI. These practices are designed to reduce that coupling and keep client experiences simpler as architectures grow.
BFF stands for Backend For Frontend, and it's a very clever way to architect your application in a way so that no matter how many different frontend experiences you offer, the core backend services remain intact.
What does this do differently? Well, sometimes you might be offering very distinct frontend experiences — multiple apps that are meant to re-use your backend services, while each offering very different UXs to your users.
Think of a limited mobile UX, vs. a feature-rich desktop app, vs. a reporting SMS interface. These are three very different UI/UX, all consuming the same business logic at their core. But if you try to create a unique and generic set of microservices that can cater to all of those UI's needs, you run the risk of introducing unnecessary coupling between the frontend and backend. This always leads to bloated and difficult-to-maintain codebases, and business logic leaking into the frontend.
Usually, you'll want your client apps to be "dumb" keeping all the "smarts" on the backend.
And how do we solve this?
On the right, you'll see the BFF pattern — creating a second layer of "backend", one that is specifically built for each UI/UX, that acts as a middleman between the "dumb" UI and the "smart" shared core backend services.
The diagram on the left shows a scenario where you had to make your client apps bigger, bloated, holding some of the business logic required to make it all work.
On the right side though, with a BFF, you can see how the implementation of a new "backend " layer per UX helps to simplify the client applications, breaking the tight coupling between the frontend and the backend services (whether internal or external APIs), allowing you to update and change them however you see fit, while keeping the client-side code intact (you simply absorb the changes in the BFFs, accordingly).
Now that you have a working understanding of the BFF pattern, let's take a look at some of the best practices around this interesting pattern.
The primary role of a BFF should be to handle the needs of its specific frontend client — and no more.
Look at the above diagram, the microservices are still there, they're the ones that own the "core business logic". The extra layer added by the BFF is only there to help solve problems that are specific to each UX.
What do I mean by that? In the above example, I proposed 3 different clients for our APIs:
- SMS
- Mobile
- Desktop app
Each of them provides a unique user experience — but what's critical here is that we're talking about serving specific needs based on the UI/UX provided, not the specific needs of each separate client implementation.
Even if, for example, there were 2 mobile apps (one for Android and one for iOS), they would still provide (more or less) the same UI/UX, and have similar data needs, therefore you'd only need one "mobile experience" BFF, not one BFF for Android, and one for iOS.
Doing it on a per-client basis would only lead to code duplication for no good reason. There is a significant difference between "the needs of a specific UI/UX" and "the needs of a specific client" that we need to remember when architecting our BFFs.
Implementing the BFF pattern can be as hard and difficult or as simple and trivial as you want.
My suggestion would be to try and find a framework that gives you the ability to easily implement this pattern without you having to do much.
In the end, what you want is a layer of abstraction over your microservices. Historically, teams used dedicated BFF frameworks to build that layer—composing services, external APIs, and databases into one client-facing interface. A BFF-oriented composition layer might, for example, join data from a weather API and a countries API into a single endpoint: the frontend calls one BFF route and gets a combined response, while the orchestration logic stays on the server.
WunderGraph pioneered this approach with one of the first open-source BFF frameworks; its product has since evolved into Cosmo , an open-source GraphQL Federation platform focused on schema management and governance. Teams scaling past custom BFF composition often move to federation-friendly patterns instead of hand-rolling orchestration in each BFF. Cosmo Connect lets teams integrate existing REST, gRPC, and other services into a federated graph without rewriting everything as GraphQL servers—a modern way to avoid reinventing the wheel. For more on that approach, see Cosmo Connect: Federation Made Accessible.
With the BFF pattern, there is always a chance that you'll end up with what is known as a "fan-out " problem.
This is when your BFF is responsible for orchestrating API requests to multiple backend services, or third-party APIs — thus introducing multiple points of failure. If any of these called downstream services fail, experience issues, or are otherwise unavailable, it can cause cascading failures in the BFF, ultimately impacting the frontend user experience.
Potential solutions to these problems include:
- Circuit breaker pattern. With this pattern, you can handle faulty and failing backend services in a controlled manner. When a backend service experiences issues, the Circuit Breaker quickly prevents the BFF from sending additional requests, reducing the time spent waiting for unresponsive services. By isolating the failing backend service this way, the Circuit Breaker prevents cascading failures from affecting other parts of the BFF or frontend application. This ensures graceful degradation during backend unavailability.
- Caching. If a backend service is unavailable, the BFF can provide cached or default data instead of returning an error. Caching frequently accessed data can also help reduce dependencies on backend services and improve response times during periods of backend unavailability — especially when used in conjunction with a Circuit Breaker.
- Service monitoring. If you're looking to understand how are your services being used (number of requests, most common requests, etc) then the BFF layer is a great place to implement logging, and monitoring the health of backend services and keep track of their availability and performance. The best part is that you can identify and resolve issues proactively with this insight.
As architectures grow, the challenge is not just avoiding fan-out failures but maintaining control over an expanding set of service dependencies, client-specific integrations, and schema changes. This is where teams often start looking for stronger governance, shared visibility, and centralized checks across their API surface area.
In my previous example, any of those externals API could've failed. If they did, they'd all report errors in wildly different ways. You need to ensure your BFF knows how to handle these errors in a consistent, unified manner, and translate those errors back to the UI in meaningful ways.
In other words, take advantage of the BFF layer and use it as an error aggregator/translator. The client app can't make sense of an HTTP 500 vs. an HTTP 200 that has an error in the JSON body — and it shouldn't.
Instead, have the BFF layer standardize error handling. That includes the format of the response, but also its content (i.e. headers, error messages, etc). That way coding the client app to feedback errors to your users in meaningful ways becomes an easier task, since all your internal error states would now be canonical as far as the client is concerned.
A NodeJS-based server such as Express.js or NestJS for your BFF lets you use TypeScript on both the frontend and the backend. Earlier versions of WunderGraph also supported this style of BFF implementation, but WunderGraph's current product direction is centered on Cosmo and GraphQL Federation.
TypeScript is a superset of JavaScript that adds static typing, which adds a layer of safety — helping you catch errors early in development, and making your code more maintainable and easier to refactor. If you can use TypeScript to develop both the frontend and the "backend" (the BFF layer), you achieve language consistency throughout the application, making it easier for developers to switch between client and server code without context switching between languages.
As Sam Newman mentions in his blog post, ideally, the frontend team should have ownership of the BFF. Using a Node-based server and TypeScript for both frontend and BFF development, lets them have both in a monorepo, making development, iteration, maintenance, testing, and deployments easier.
In fact, TypeScript can also allow you to share certain code, types, and interfaces between the frontend and the "backend" (actually the BFF). For example, if you have validation logic that is shared between the client app and the BFF, TypeScript can ensure that these shared components are correctly used in both places.
Here is an article on how to do just that.
Type safety is no longer just about sharing TypeScript types in a monorepo. Teams increasingly rely on schema contracts, composition checks, and controlled rollout mechanisms such as Graph Feature Flags to evolve client-facing APIs safely across multiple services and teams.
In the end, BFF is nothing fancy or strange, but rather, yet another layer of abstraction that sits between your UI and the core business logic.
By incorporating these five best practices, you'll be well on your way to building scalable, maintainable, and performant BFFs.
If your architecture is at the point where multiple BFF layers are solving the same composition problems, GraphQL Federation is worth evaluating.
Frequently Asked Questions (FAQ)
In 2026, a Backend for Frontend (BFF) is still a client-specific API layer that tailors responses for web, mobile, or other interfaces. What has changed is that many teams now combine that idea with federated APIs and stronger schema governance instead of relying only on custom backend aggregation code. The core principle remains the same—keeping frontend clients simple by handling orchestration, transformation, and business logic in a dedicated backend layer—but the implementation patterns have evolved toward federation-friendly architectures.
A BFF is a good fit when one client experience needs custom response shaping, lightweight orchestration, or client-specific policies that don't apply to other consumers. GraphQL Federation becomes more attractive when many teams and services need to contribute to one shared API contract with centralized composition, schema checks, and governance. If you're managing 2-3 services for a single client type, BFF is often simpler. If you're coordinating 5+ services across multiple teams with shared schema concerns, federation typically scales better.
A BFF typically sits between frontend clients and underlying microservices, aggregating data from multiple services and translating backend responses into shapes the client can use more easily. The BFF absorbs changes to downstream services, preventing those changes from cascading into client code. This simplifies frontend development and keeps business logic centralized. However, as the number of services grows, teams often find they're duplicating orchestration logic across multiple BFFs, which pushes them toward federation or other shared composition models where services contribute directly to one unified graph.
The BFF pattern solves frontend-backend coupling problems. Without a BFF, frontend changes often require backend API changes, and backend evolution can break client experiences or force business logic into the UI. A BFF acts as a translation and orchestration layer that insulates the frontend from backend service changes, handles data aggregation from multiple services, normalizes error responses, and optimizes API payloads for each client type's specific needs. This keeps clients "dumb" and backends focused on core business logic.
Yes, the BFF pattern is still relevant for teams that need client-specific API tailoring, but the implementation approach has evolved. While dedicated per-client BFF servers were common in the past, modern architectures increasingly use GraphQL Federation, edge computing, and shared composition layers to solve the same problems at scale. BFFs remain useful for edge concerns, client-specific policies, and presentation shaping, but many teams now adopt federation when multiple BFFs start solving the same cross-service composition challenges.

