Backend for Frontend
Architecture Diagram
The One API Problem
Most teams start with one API that serves everything. The React app, the iOS app, maybe an Android app, maybe a partner integration. And honestly, it works great for a while. You have one codebase, one deployment, one set of endpoints. Life is simple.
Then the cracks show up. Your mobile team complains that the /orders endpoint returns 47 fields when they only display 6. That is real bandwidth on a 3G connection in a subway. Your web team wants nested user profiles and order history in a single call for their dashboard, but the API only returns IDs that require three follow-up requests. And your partner integration team keeps asking you to not change the response shape because their systems take weeks to adapt.
You start adding query parameters. ?fields=id,name,total for mobile. ?include=user,items for web. ?version=2 for partners. Before long, your "simple" API has a combinatorial explosion of response shapes, and every change requires testing against every consumer. You have built a general-purpose API that is mediocre for everyone.
What a BFF Actually Is
A BFF is a thin server-side layer that sits between a specific client type and your backend services. The mobile BFF calls the same user service, order service, and product service that everyone else does. But it knows that the mobile home screen needs the user's first name, their three most recent orders (just the order ID and total), and the top 5 recommended products (just the image URL and price). So it fetches from three services, cherry-picks the relevant fields, and returns one tight JSON payload.
The web BFF does something completely different. It knows the dashboard page needs the full user profile with settings, the last 50 orders with line items, analytics data, and notification counts. It makes more calls, returns a richer response, and maybe even pre-renders some HTML for server-side rendering.
This is not a proxy. A proxy passes things through. This is not an API gateway, which handles cross-cutting concerns like rate limiting and auth. A BFF understands its client. It speaks the client's language, returns the client's data shapes, and evolves on the client's release cadence.
When BFF Helps
You need genuinely different data shapes for different clients. Not theoretically different. Actually different, today, causing real pain.
The classic case is a company with a mobile app and a web app hitting microservices. The mobile app has tight performance budgets and shows condensed views. The web app has room for rich, detailed interfaces. Trying to serve both well from one API means compromises for both.
BFF also helps when you have a large enough team structure that separate groups can own separate BFFs. At Spotify, different squads own different parts of the mobile experience, and the BFF layer lets each squad control exactly what data their features receive. At SoundCloud, they adopted BFF specifically because their mobile and web teams kept stepping on each other through a shared API.
High traffic is another trigger. When you are serving millions of mobile requests, shaving 2KB off every response adds up fast. A BFF that strips unnecessary fields and pre-computes derived values saves real money on CDN and bandwidth costs.
When BFF Adds Overhead
If you have one client, stop reading this article and go build features. A single-page web app talking to a handful of backend services does not need a BFF. You are adding a network hop, a deployment, and an on-call rotation for no reason.
Small teams feel this acutely. If you have 4 engineers, maintaining two or three BFF services on top of your actual backend means context-switching constantly. Your velocity drops. You ship slower. The optimization you gained on payload size is dwarfed by the organizational cost of managing more services.
Simple data needs are another disqualifier. If your clients mostly need the same data and the differences are minor, a single API with sparse fieldsets (like JSON:API or a GraphQL endpoint) handles this cleanly. BFF exists for when the differences are structural, not cosmetic.
And if your product is still finding product-market fit, the last thing you want is three API layers to update every time you pivot a feature. Move fast, ship one API, and revisit BFF when your client divergence becomes a real bottleneck.
Who Owns the BFF
This is where most BFF adoptions succeed or fail, and it has nothing to do with technology.
The frontend team owning the BFF is usually the right call. They know what data their screens need. They can ship a BFF change and a client change in the same sprint. They do not have to file a ticket with the backend team, wait two weeks, do a review cycle, and then discover the response shape is wrong. The catch is that frontend engineers need to be comfortable writing server-side code. In practice, Node.js BFFs lower this bar significantly since the same team can write TypeScript on both sides.
Backend team ownership creates a bottleneck. Every UI change that needs a different data shape becomes a cross-team dependency. The backend team prioritizes their own roadmap, and frontend requests pile up. This pattern has killed BFF adoption at multiple companies because the frontend team just starts calling backend services directly to work around the bottleneck.
Shared ownership sounds reasonable in a design document and falls apart in practice. Nobody feels responsible. Code reviews are slow because both teams need to approve. Deployment coordination is painful. If you go this route, at least assign a clear on-call rotation and make one team the tiebreaker on design decisions.
BFF vs GraphQL
GraphQL and BFF solve overlapping problems from opposite directions.
GraphQL says: give clients a flexible query language and let them ask for exactly what they need. The server exposes a schema, and each client writes queries tailored to their screens. This is powerful. No need for separate API layers per client. One schema, many query shapes.
BFF says: the server knows what the client needs and gives it exactly that. The client makes a simple REST call and gets a pre-shaped response. The server has full control over performance characteristics, caching, and data aggregation.
GraphQL works beautifully when you have many different views that need many different data combinations. Think a content platform where every page layout is configurable, or a developer tool where users build custom dashboards. The combinatorial query space is huge, and predicting every data shape on the server is impractical.
BFF works better when you have a small number of client types with predictable, stable needs. Mobile home screen, mobile detail screen, web dashboard, web settings. You know these shapes. You can optimize them aggressively. You do not need the overhead of a query language.
And here is the thing people miss: you can use both. A GraphQL server owned by the frontend team is basically a BFF with a query language. Some teams run a lightweight Apollo Server as their BFF layer, giving frontend engineers schema ownership while keeping backend services as plain REST or gRPC. This gets you the developer experience of GraphQL with the organizational clarity of BFF ownership.
Common Mistakes
The number one mistake is letting domain logic leak into the BFF. The BFF should aggregate and reshape, nothing more. If you find pricing calculations, inventory checks, or permission evaluations in a BFF, you have a problem. That logic belongs in the domain services. Once it lives in the BFF, you have to keep it in sync across every BFF, and you will not.
Duplicating domain logic across BFFs is the inevitable consequence of the first mistake. Your mobile BFF calculates tax one way, your web BFF calculates it slightly differently, and now you have a bug that only affects one platform. Customers notice. Support tickets pile up. Someone spends a week tracking down why the mobile order total does not match the web order total.
Skipping shared infrastructure libraries is another common one. Auth middleware, structured logging, circuit breakers, error response formatting. These should be shared packages that every BFF imports. Writing them from scratch in each BFF means they drift apart. Your mobile BFF logs errors in one format, your web BFF in another, and your observability pipeline cannot correlate them.
Finally, a lot of organizations treat BFF as "just another backend microservice" and put it in the backend team's backlog. This defeats the entire purpose. The BFF exists to decouple frontend iteration speed from backend release cycles. If it is owned, prioritized, and deployed by the backend team, you have just added a network hop to your monolith.
Key Points
- •A BFF is not a proxy or a gateway. It is a dedicated aggregation and reshaping layer that knows exactly what its client needs and returns nothing more
- •The team that builds the client should own its BFF. When backend teams own it, every frontend change becomes a cross-team ticket and velocity tanks
- •BFF shines when your mobile app needs 3 fields from 4 services on a single screen. Without it, you either over-fetch or make the client orchestrate multiple calls on a cellular connection
- •You can combine BFF with GraphQL. Some of the best setups use a thin GraphQL layer as the BFF, giving the frontend team schema ownership while keeping backend services clean
- •If you only have one client type, you almost certainly do not need a BFF. A well-designed API with field selection will serve you better with half the operational surface area
Common Mistakes
- ✗Letting business logic creep into the BFF layer. Once someone puts a discount calculation in the web BFF, you will find a different discount calculation in the mobile BFF within a month
- ✗Building separate BFFs when your mobile and web clients actually need the same data. BFF solves divergent needs, not hypothetical future divergence
- ✗Skipping shared libraries for authentication, logging, and error handling across BFFs. You end up with three different auth implementations that drift apart over time
- ✗Treating the BFF as a backend team deliverable. It sits in the frontend team's critical path and should be planned, prioritized, and deployed on their cadence