Skip to content
Low Level Design Mastery Logo
LowLevelDesign Mastery

Backend for Frontend (BFF)

One backend per frontend, optimized for each

Traditional approach: One API for all clients.

Diagram

Problems:

  • ❌ Mobile gets too much data (wastes bandwidth)
  • ❌ Web doesn’t get enough features
  • ❌ Can’t optimize for specific clients
  • ❌ One API tries to serve everyone

Solution: Backend for Frontend (BFF) - One backend per frontend!


BFF = Each client type gets its own tailored backend API.

Diagram

Each BFF:

  • ✅ Tailored to specific client needs
  • ✅ Optimizes data for that client
  • ✅ Provides client-specific features
  • ✅ Can use different protocols

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!)


Each client has dedicated BFF:

Diagram

Pros:

  • ✅ Complete isolation
  • ✅ Independent deployment
  • ✅ Different tech stacks possible

Cons:

  • ❌ More infrastructure
  • ❌ Code duplication risk

One BFF with client-specific adapters:

Diagram

Pros:

  • ✅ Shared code
  • ✅ Less infrastructure
  • ✅ Easier maintenance

Cons:

  • ❌ Coupling between clients
  • ❌ Harder to scale independently

"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)
"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)

Adapters transform data for each client:

"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)

  • Clients have very different needs
  • Mobile needs differ significantly from web
  • Performance optimization needed per client
  • Client-specific features required
  • Different protocols needed (REST for web, gRPC for mobile)
  • Clients have similar needs
  • Simple API with few differences
  • Limited resources (BFF adds complexity)
  • Small team (harder to maintain multiple backends)

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.