API Gateway vs BFF: When to Use Which
An API Gateway and a Backend for Frontend are both API layers, but they solve different problems. Here's how to choose — and why you might need both.
When your frontend talks to a microservices backend, you need some kind of intermediary. Two patterns keep coming up: the API Gateway and the Backend for Frontend (BFF). They look similar from the outside — both are layers between clients and services — but they serve fundamentally different purposes.
Choosing the wrong one (or conflating them) leads to bloated gateways, over-coupled frontends, or performance bottlenecks. Let’s sort it out.
The API Gateway
An API Gateway is a single entry point for all clients. It handles cross-cutting concerns that every API request needs:
- Authentication & authorization
- Rate limiting
- SSL termination
- Request routing
- Load balancing
- Logging & monitoring
- Caching
Client A (Web) ─┐
Client B (Mobile)─┤──→ [API Gateway] ──→ Service A
Client C (IoT) ─┘ ──→ Service B
──→ Service C
The Gateway doesn’t know or care what the client is. It just enforces rules and routes traffic.
// Typical gateway config (e.g., in Kong or a custom Express gateway)
const routes = [
{
path: '/api/orders',
target: 'http://order-service:3001',
middleware: [authenticate, rateLimit({ max: 100, window: '1m' })],
},
{
path: '/api/users',
target: 'http://user-service:3002',
middleware: [authenticate, rateLimit({ max: 200, window: '1m' })],
},
];
What an API Gateway is NOT good at
The API Gateway is generic by design. It shouldn’t aggregate data from multiple services for a specific client. It shouldn’t know that your mobile app needs a different data shape than your web app. As soon as you start adding client-specific logic to a gateway, you’ve created a monster.
The Backend for Frontend (BFF)
A BFF is a dedicated backend for a specific frontend. It’s not a proxy — it’s a real application that speaks the language of its client.
The concept, popularized by Sam Newman, acknowledges that different clients have different needs:
- A mobile app needs small payloads and optimized queries for slow networks
- A web app might need rich, aggregated data for complex UI states
- A third-party API consumer needs a stable, versioned contract
[Mobile App] ──→ [Mobile BFF] ──→ Service A
──→ Service B
[Web App] ──→ [Web BFF] ──→ Service A
──→ Service C
──→ Service D (aggregated)
[3rd Party] ──→ [API Gateway] ──→ Service A
──→ Service B
What a BFF does that a Gateway can’t
// Web BFF: aggregates data from multiple services for a dashboard
async function getDashboard(userId: string): Promise<DashboardData> {
const [user, orders, recommendations] = await Promise.all([
userService.getUser(userId),
orderService.getRecentOrders(userId, { limit: 5 }),
recommendationService.getPersonalized(userId),
]);
return {
user: { name: user.name, avatar: user.avatar },
recentOrders: orders.map(formatOrderForWeb),
recommendations: recommendations.slice(0, 10),
};
}
// Mobile BFF: same data, but stripped down for mobile
async function getMobileDashboard(userId: string): Promise<MobileDashboardData> {
const [user, orders] = await Promise.all([
userService.getUser(userId),
orderService.getRecentOrders(userId, { limit: 3 }),
]);
return {
userName: user.name,
orderCount: orders.length,
lastOrderStatus: orders[0]?.status,
};
}
The BFF does the heavy lifting so the frontend doesn’t have to.
Comparing the Two
| Concern | API Gateway | BFF |
|---|---|---|
| Audience | All clients | One specific client |
| Auth/rate limiting | ✅ Yes | Delegates to gateway |
| Data aggregation | ❌ No | ✅ Yes |
| Business logic | ❌ No | Sometimes |
| Client-specific shape | ❌ No | ✅ Yes |
| Owned by | Platform/infra team | Frontend team |
Using Both Together
In mature architectures, you often use both:
[Web App] ──→ [Web BFF] ──→ [API Gateway] ──→ Services
[Mobile App] ──→ [Mobile BFF] ──→ [API Gateway] ──→ Services
The Gateway handles auth, rate limiting, SSL, and routing to BFFs. The BFF handles aggregation, transformation, and client-specific concerns.
The BFF team (usually the frontend team) doesn’t need to worry about auth infrastructure. The Gateway team doesn’t need to know what data the mobile app wants.
When to Skip the BFF
The BFF pattern adds complexity: another service to deploy, maintain, and monitor. Don’t reach for it unless you need it.
Skip the BFF if:
- You have a single client
- Your clients have nearly identical data needs
- Your team is small and can’t afford the overhead
- Your services already return client-friendly shapes
Add a BFF when:
- Different clients need fundamentally different data shapes
- You’re making multiple service calls per UI screen (N+1 problem)
- Frontend teams want autonomy over their API layer
- Mobile clients are performance-sensitive
Common Mistakes
Putting business logic in the gateway: gateways should be dumb pipes. If you’re computing discounts in your gateway, you’ve built a new monolith.
One BFF for all clients: the whole point of BFF is per-client customization. A “universal BFF” is just a poorly designed API.
Skipping the gateway: BFFs still need auth, rate limiting, and SSL. Don’t duplicate that in every BFF — put it in a gateway.
Key Takeaways
- API Gateway = cross-cutting concerns for all traffic (auth, rate limiting, routing)
- BFF = client-specific aggregation and transformation
- They’re complementary, not competing — many systems use both
- The BFF is owned by the frontend team; the gateway by the platform team
- Add a BFF when different clients have meaningfully different needs