What is the BFF
BFF stands for Backend For Frontend. It’s a pattern coined by Sam Newman around 2015, and the core idea is simple: instead of building one general-purpose backend API for all clients, create a dedicated backend service for each user experience.
The way I see it, it’s a translator sitting between the frontend and backend. It knows what your frontend looks like, what data it needs, and when — then fetches everything from the backend, assembles it, and hands it back clean.

Why the BFF even exists
Think about a typical scenario from a few years back: your company starts with a desktop web app. All backend APIs are designed with that one web client in mind. Then mobile arrives — iOS and Android apps need to be built.
Here come the problems. Mobile screens are smaller, they can’t display as much information as desktop. The data requirements are completely different. Plus mobile needs to worry about battery, bandwidth, and data caps — things the desktop never cared about.
The features themselves might differ too. On desktop you browse and order at your leisure. On mobile you might need barcode scanning for price comparison or in-store navigation. If the backend has just one general-purpose API, it either bloats up serving every client’s needs, or becomes a team bottleneck — every API change goes through a queue.
That’s when the BFF pattern saves the day.
Core principles
There’s a line in Sam Newman’s original article that I really like: “one experience, one BFF.” One user experience maps to one backend service built for that frontend.
E-commerce example:
– Desktop web gets its own BFF service
– iOS app gets its own
– Android app gets its own
Each dedicated backend serves only its corresponding frontend. Data format, endpoint count, aggregation logic — all driven by what that frontend actually needs. No more “why does the Android feature break iOS endpoints?”
SoundCloud took a slightly different approach: iOS and Android shared one mobile layer, with a separate one for desktop. The catch is, if iOS and Android diverge significantly in user experience, sharing creates friction.
The better rule is: **align these services with team boundaries.** If one team owns both iOS and Android, one shared service is fine. Two independent teams? Keep them separate.
What this layer actually does
1. Data aggregation
In a microservice architecture, a single page often pulls data from multiple services. A product detail page — product info from a product service, stock from inventory, pricing from a pricing service, recommendations from a recommendation service.
Without a middle layer, the frontend fires 4 requests and stitches them together. With it, one request goes out, the aggregation layer fetches all 4 downstream services in parallel, and returns the assembled result.
2. Data trimming
Same user info. On desktop list view you need name, email, avatar, role. On mobile list view you just need name and avatar. The BFF trims the response based on which client is asking.
3. Protocol translation
Your downstream might use gRPC, SOAP, or even old XML endpoints. This service translates it all into frontend-friendly JSON REST or GraphQL. The frontend never needs to know what’s happening on the other side.
4. Authentication and session management
It can also act as an auth gateway — handling login, token refresh, permission checks. The frontend gets clean, authenticated data.
BFF vs API Gateway
This is the most common question. Here’s the breakdown:
| Dimension | API Gateway | BFF Layer |
|---|---|---|
| Purpose | General routing gateway | Frontend-specific backend |
| Audience | All clients | Specific frontend experience |
| Responsibilities | Routing, rate limiting, auth, logging | Data aggregation, trimming, UI adaptation |
| Maintained by | Infrastructure team | Frontend team |
| Code style | Config-driven, generic | Tightly coupled to frontend |
In production, they often work together: request hits Gateway (rate limiting, auth), then the BFF layer (data aggregation), then downstream microservices. The gateway doesn’t replace the frontend backend — they solve different problems.
Node.js in the picture
What language do frontend teams know best? JavaScript and TypeScript. Building this backend layer with Node.js means the frontend team owns it end to end. No cross-team communication overhead. That’s why so many teams choose Node.js for this role.
Popular choices:
– Next.js API Routes / Server Actions — if you’re already on React, this is the most natural fit
– NestJS — TypeScript full-stack framework for heavier setups
– Express / Fastify — lightweight, roll your own
Quick Express example:
```javascript
// compose product details for the frontend
app.get('/api/product/:id', async (req, res) => {
const [product, stock, price] = await Promise.all([
fetch(`http://product-service/products/${req.params.id}`),
fetch(`http://inventory-service/stock/${req.params.id}`),
fetch(`http://pricing-service/price/${req.params.id}`),
]);
const data = {
...(await product.json()),
stock: (await stock.json()).quantity,
price: (await price.json()).amount,
};
// Mobile doesn't need all fields
if (req.headers['x-client'] === 'mobile') {
delete data.description;
delete data.specs;
}
res.json(data);
});
```
Common pitfalls
1. It becomes a “fat layer”
This service is supposed to be thin. If you cram business logic in there, it becomes another single point of failure. It should handle presentation-related logic and client adaptation — nothing more.
2. Added latency
Every extra hop costs time. If you don’t parallelize downstream calls, the serial overhead adds up fast. Always use Promise.all or equivalent for independent fetches.
3. Team ownership confusion
If the backend team maintains this layer, you’ve missed the point. The whole value here is that the frontend team owns it — releases at their pace, priorities driven by their UX. If changing a display field requires queuing with the backend team, there’s no benefit.
4. Over-engineering
A simple CRUD admin panel with one frontend client doesn’t need this. Adding a layer with zero benefit just creates maintenance overhead. It makes sense when you have multiple clients or distributed data sources — don’t jump on it for every project.
When to use this pattern
Based on Sam Newman’s original article and Microsoft’s Azure Architecture guide:
Good fit:
– Multiple frontend clients (Web, iOS, Android) with differing needs
– Microservice backend with distributed data sources
– Frontend team wants to own the API contract
Bad fit:
– Only one frontend client
– All clients have nearly identical requirements
– Project is still in MVP — speed trumps architectural purity
Wrapping up
This isn’t a flashy architectural pattern. It exists because of a real-world problem: in a multi-client world, general-purpose backends can’t keep up with every frontend’s unique requirements.
Its real value is moving API ownership from the backend to the frontend team. Building this kind of dedicated service isn’t about making frontend engineers write backend code — it’s about letting the people who know the user experience best decide how data is organized, assembled, and returned.
But it’s not free. An extra layer means an extra network hop, an extra service to deploy, an extra dimension of complexity. Whether you need it, and when, depends on your actual scenario — not on architectural trends.
Links:
– Sam Newman — Backends For Frontends
– Microsoft Azure — BFF Pattern
📖 Recommended Reading
Take a look at these articles, you may be interested
RSC(React Server Components) Guide — How They Work, Code Examples, and Framework Comparison
Introductory Analysis: Why Next.js 15 Became the Standard for Agentic AI Apps
Web Components + Astro + htmx: The 2026 Lightweight Frontend Trio
React Compiler 1.0 Is Here: Can We Finally Delete useMemo and useCallback?