Skip to content
LowLevelDesign Mastery

Backend for Frontend (BFF)

One backend per frontend, optimized for each

Traditional approach: One API for all clients.

Mobile, web and desktop apps sharing one generic API, over-fetching for mobile and under-serving web and desktop needs

Problems:

  • Mobile gets too much data (wastes bandwidth) - Mobile apps receive full user objects with all fields, wasting precious bandwidth on mobile networks
  • Web doesn’t get enough features - Web apps need richer data and more features, but the generic API doesn’t provide them
  • Can’t optimize for specific clients - One API can’t be optimized for both mobile (minimal data) and web (rich features) simultaneously
  • One API tries to serve everyone - Attempting to satisfy all clients leads to compromises that satisfy none perfectly

Solution: Backend for Frontend (BFF) - Each client type gets its own tailored backend API, optimized for its specific needs and constraints.


Backend for frontend layer with mobile, web and desktop BFFs aggregating calls to shared user, order and product services

BFF (Backend for Frontend) is an architectural pattern where each client type (mobile, web, desktop) gets its own tailored backend API optimized for its specific needs. Instead of forcing all clients to use a one-size-fits-all API, BFF creates specialized backends that understand each client’s requirements.

The BFF pattern recognizes that different clients have fundamentally different needs:

  • Mobile apps need minimal data to save bandwidth and battery
  • Web apps need rich data and features for complex UIs
  • Desktop apps might need different protocols or data formats

By creating a dedicated backend for each frontend, you can optimize data transfer, reduce over-fetching, and provide client-specific features without compromising any client’s experience.

Mobile, web and desktop apps each calling a dedicated BFF optimized for its data and features, all aggregating shared backend services

Each BFF:

  • Tailored to specific client needs - Understands the client’s constraints and requirements
  • Optimizes data for that client - Returns only the data needed, in the optimal format
  • Provides client-specific features - Can include features unique to that client type
  • Can use different protocols - Mobile might use gRPC for efficiency, web might use REST for simplicity

Problem of one API for mobile and web needs, solved by separate mobile and web BFFs, giving per-client optimization and faster development

Mobile needs:

{
"id": 123,
"name": "John",
"avatar": "small_url"
}

Size: ~100 bytes

Web needs:

{
"id": 123,
"name": "John Doe",
"email": "[email protected]",
"avatar": "large_url",
"bio": "Long bio text...",
"preferences": {...},
"recentActivity": [...],
"friends": [...]
}

Size: ~5000 bytes

Without BFF: Mobile gets 5000 bytes (wastes 4900 bytes!)
With BFF: Mobile gets 100 bytes (50x smaller!)


BFF architecture patterns: one BFF per client for separation, shared backend services for reuse, and adapters to transform responses

Each client has dedicated BFF:

Separate BFF per client with mobile app calling a mobile BFF at /api/mobile and web app calling a web BFF at /api/web

Pros:

  • Complete isolation - Each BFF is completely independent, allowing changes without affecting others
  • Independent deployment - Deploy mobile BFF updates without touching web BFF
  • Different tech stacks possible - Mobile BFF could use Node.js, web BFF could use Python

Cons:

  • More infrastructure - Need to deploy and maintain multiple BFF services
  • Code duplication risk - Common logic might be duplicated across BFFs (mitigate with shared libraries)

One BFF with client-specific adapters:

Single shared BFF receiving mobile and web requests and passing them to mobile and web adapters that call backend services

Pros:

  • Shared code - Common logic is shared, reducing duplication
  • Less infrastructure - Single BFF service to deploy and maintain
  • Easier maintenance - One codebase to update for shared features

Cons:

  • Coupling between clients - Changes to one adapter might affect others
  • Harder to scale independently - Can’t scale mobile and web BFFs separately

Mobile BFF receiving minimal cached data and web BFF receiving full data with real-time features from the same backend services
💡 Tip: Click dropdown to switch between languages
"mobile_bff.py
from flask import Flask, jsonify
import requests
app = Flask(__name__)
SERVICES = {
'users': 'http://user-service:8001',
'orders': 'http://order-service:8002',
}
@app.route('/api/mobile/users/<user_id>')
def get_mobile_user(user_id):
"""Mobile-optimized user endpoint - minimal data"""
# Fetch from user service
user_response = requests.get(f"{SERVICES['users']}/users/{user_id}")
user = user_response.json()
# Transform for mobile (minimal data)
mobile_user = {
'id': user['id'],
'name': user['name'],
'avatar': user.get('avatar_small'), # Small avatar for mobile
}
return jsonify(mobile_user)
@app.route('/api/mobile/users/<user_id>/orders')
def get_mobile_orders(user_id):
"""Mobile-optimized orders - summary only"""
orders_response = requests.get(
f"{SERVICES['orders']}/orders?userId={user_id}"
)
orders = orders_response.json()
# Transform for mobile (summary only)
mobile_orders = [
{
'id': order['id'],
'total': order['total'],
'status': order['status'],
'date': order['created_at'][:10] # Just date, not full timestamp
}
for order in orders
]
return jsonify(mobile_orders)
💡 Tip: Click dropdown to switch between languages
"web_bff.py
from flask import Flask, jsonify
import requests
app = Flask(__name__)
SERVICES = {
'users': 'http://user-service:8001',
'orders': 'http://order-service:8002',
'products': 'http://product-service:8003',
}
@app.route('/api/web/users/<user_id>')
def get_web_user(user_id):
"""Web-optimized user endpoint - full data"""
# Fetch from multiple services
import concurrent.futures
with concurrent.futures.ThreadPoolExecutor() as executor:
user_future = executor.submit(
requests.get, f"{SERVICES['users']}/users/{user_id}"
)
orders_future = executor.submit(
requests.get, f"{SERVICES['orders']}/orders?userId={user_id}"
)
preferences_future = executor.submit(
requests.get, f"{SERVICES['users']}/users/{user_id}/preferences"
)
user = user_future.result().json()
orders = orders_future.result().json()
preferences = preferences_future.result().json()
# Transform for web (full data)
web_user = {
'id': user['id'],
'name': user['name'],
'email': user['email'],
'avatar': user.get('avatar_large'), # Large avatar for web
'bio': user.get('bio'),
'preferences': preferences,
'recentOrders': orders[:5], # Recent orders
'stats': {
'totalOrders': len(orders),
'totalSpent': sum(o['total'] for o in orders),
}
}
return jsonify(web_user)

BFF adapter transforming a full nested backend user object into an optimized client response with only the needed fields

Adapters transform data for each client:

💡 Tip: Click dropdown to switch between languages
"adapters.py
from abc import ABC, abstractmethod
from typing import Dict, Any
class UserAdapter(ABC):
"""Base adapter interface"""
@abstractmethod
def transform(self, user: Dict[str, Any]) -> Dict[str, Any]:
"""Transform user data for specific client"""
pass
class MobileUserAdapter(UserAdapter):
"""Adapter for mobile - minimal data"""
def transform(self, user: Dict[str, Any]) -> Dict[str, Any]:
return {
'id': user['id'],
'name': user['name'],
'avatar': user.get('avatar_small'),
}
class WebUserAdapter(UserAdapter):
"""Adapter for web - full data"""
def transform(self, user: Dict[str, Any]) -> Dict[str, Any]:
return {
'id': user['id'],
'name': user['name'],
'email': user['email'],
'avatar': user.get('avatar_large'),
'bio': user.get('bio'),
'preferences': user.get('preferences', {}),
'stats': user.get('stats', {}),
}
class BFFService:
"""BFF service using adapters"""
def __init__(self, adapter: UserAdapter):
self.adapter = adapter
def get_user(self, user_id: str) -> Dict[str, Any]:
# Fetch from backend service
user = user_service.get_user(user_id)
# Transform using adapter
return self.adapter.transform(user)

Decision for using BFF with multiple client types and independent teams versus skipping it for a single client or small team
  • Clients have very different needs - Mobile and web require fundamentally different data structures and features
  • Mobile needs differ significantly from web - Mobile needs minimal data, web needs rich features
  • Performance optimization needed per client - Each client has different performance requirements
  • Client-specific features required - Some features are only relevant to specific client types
  • Different protocols needed (REST for web, gRPC for mobile) - Different clients benefit from different communication protocols
  • Clients have similar needs - If all clients need the same data, BFF adds unnecessary complexity
  • Simple API with few differences - The overhead of maintaining multiple BFFs isn’t justified
  • Limited resources (BFF adds complexity) - BFF requires more infrastructure and maintenance
  • Small team (harder to maintain multiple backends) - Multiple BFFs require more development and operational effort

BFF versus API gateway, contrasting client-specific transformation with cross-cutting entry point, used together as gateway to BFF to services
FeatureAPI GatewayBFF
PurposeRouting, cross-cutting concernsClient-specific optimization
AggregationCan aggregateAlways aggregates
TransformationMinimalHeavy transformation
Client AwarenessGenericClient-specific
Use CaseSingle entry pointClient optimization

They can work together:

Client → API Gateway → BFF → Services

Client-Specific

BFF provides tailored APIs for each client type. Mobile gets minimal data, web gets full features.

Performance Optimized

Each BFF optimizes for its client. Mobile saves bandwidth, web gets rich data.

Adapter Pattern

Use adapter pattern to transform data for each client. Shared logic, client-specific transformations.

Code Reuse

Share common logic in libraries. BFFs can share code while providing client-specific APIs.