| name | bff-pattern |
| description | When and how to use a Backend-for-Frontend (BFF) for mobile -- scoping, ownership, and anti-patterns. Use when deciding whether a BFF is justified or designing one. |
Backend-for-Frontend (BFF) Pattern
Instructions
A BFF is a thin service dedicated to one client class (here, the mobile app). It composes calls to internal microservices and shapes payloads to match the app's screens. A BFF is a force multiplier when internal services are generic and the app makes many round-trips; it becomes a liability if it hides business rules or duplicates them.
1. Rationale
Introduce a mobile BFF when two or more of these are true:
- A single screen requires 3+ calls to internal services that could be composed server-side.
- Internal service payloads carry fields the mobile app should never see (PII, internal flags).
- The mobile team needs to move faster than internal service teams (different release cadence).
- Different client classes (web, mobile, TV) need meaningfully different payloads from the same backend truth.
Do not introduce a BFF if the underlying services already return screen-appropriate data, or if the BFF would merely proxy without composition.
2. Scoping
One BFF per client class. Naming: mobile-bff, web-bff. Avoid a single "omni-bff" -- it re-creates the coupling you left behind.
┌─────────────┐
Mobile -> │ mobile-bff │ -> users-svc, feed-svc, search-svc, media-svc
└─────────────┘
┌─────────────┐
Web -> │ web-bff │ -> users-svc, feed-svc, search-svc
└─────────────┘
3. Ownership
The mobile team owns mobile-bff. This is non-negotiable; otherwise it becomes another backend team queue.
- Same repo as the app is acceptable for small teams; separate repo + shared CI at scale.
- Release train coupled to the mobile app (or slightly ahead -- the BFF ships first so app rollout can fall back to a field flag).
4. What Belongs in a BFF
- Composition / fan-out to internal services.
- Payload shaping (field selection, renaming, flattening for the screen).
- Client-specific auth glue (exchanging an app-issued session for internal service tokens).
- Feature flag evaluation for the client.
- Per-screen caching (short TTL, edge-friendly).
5. What Does Not Belong
- Business rules (pricing, permissions, workflow state) -- those live in the domain services.
- Data ownership -- the BFF has no database of its own beyond a cache.
- Cross-client logic -- if two BFFs need the same rule, push it down.
6. Example Endpoint
A home screen that currently needs: /users/me, /feed?user=me, /stories/live, /notifications/unread.
BFF endpoint:
GET /mobile/v1/home
Authorization: Bearer ...
Kotlin (Ktor) composition:
suspend fun ApplicationCall.home() {
val userId = principal<UserPrincipal>()!!.id
coroutineScope {
val user = async { users.me(userId) }
val feed = async { feed.top(userId, limit = 20) }
val live = async { stories.live(userId) }
val unread = async { notifications.unread(userId) }
respond(HomePayload(user.await(), feed.await(), live.await(), unread.await()))
}
}
Client consumption (Swift):
struct Home: Decodable { let user: User; let feed: [Article]; let live: [Story]; let unread: Int }
let home: Home = try await api.get("/mobile/v1/home")
One call, shaped exactly for the screen. The underlying four services remain untouched and generic.
7. Reliability Patterns
- Timeouts: per downstream, aggressive (e.g., 300 ms). The BFF assembles a partial response if a non-critical service is slow.
- Fallbacks: return the screen with
unread: null rather than failing the whole call.
- Bulkheads: separate connection pools per downstream so one slow service cannot starve the others.
- Circuit breakers: open on repeated downstream failure; return cached/empty shapes.
8. Caching
Short, per-user TTLs (seconds, not minutes) keyed by (endpoint, user_id, feature_flags). Invalidation on mutation is usually not worth the complexity.
9. Versioning
Because the BFF serves one client, version it in lockstep with the app:
/mobile/v1/* freezes when /mobile/v2/* ships.
- Retire
v1 after the minimum-supported app version drops below that release.
Checklist