All Posts

Backend for Frontend (BFF): Tailoring APIs for UI

Mobile apps need less data than Desktop dashboards. Why serve them the same JSON? The Backend for Frontend (BFF) pattern solves this.

Abstract AlgorithmsAbstract Algorithms
Β·Β·11 min read
Share
Share on X / Twitter
Share on LinkedIn
Copy link

TLDR: A "one-size-fits-all" API causes bloated mobile payloads and underpowered desktop dashboards. The Backend for Frontend (BFF) pattern solves this by creating a dedicated API server for each client type β€” the mobile BFF reshapes data for small screens, the web BFF aggregates for dashboards.


πŸ“– The One-Size-Fits-All API Problem

You have a mobile app and a web dashboard. Both call the same REST API. The problem:

  • Mobile loads a user profile page. The generic /user/{id} endpoint returns 50 fields. The mobile app uses 8 of them. The remaining 42 fields are wasted bytes over a 4G connection.
  • Web dashboard needs aggregated data for a rich homepage. Instead of one call, the frontend makes 6 separate API calls (user, orders, recs, notifications…), assembles the response on the client, and shows a spinner for 3 seconds.

Both problems come from forcing a single API to serve very different clients.


πŸ” BFF Basics: One Backend Per Frontend

A generic REST API returns the same response shape to every caller. That works for simple apps but breaks down when clients have radically different needs:

  • Mobile apps run on limited bandwidth and battery. They need compact, pre-computed responses.
  • Web dashboards need rich, aggregated data combining multiple domain objects.
  • Third-party integrations may need specific authentication flows or data formats.

The BFF pattern solves this with a simple rule: each client type gets its own backend.

ComponentRoleWho Owns It
Mobile BFFServes the mobile appMobile team
Web BFFServes the web dashboardWeb team
Partner BFFServes external API consumersPlatform team
Shared MicroservicesDomain business logicBackend platform team

The BFF is a translation layer β€” it speaks the client's language on one side and the microservice language on the other. Business logic stays in the microservices.


βš™οΈ One API Layer per Client Type

The BFF pattern introduces a thin, client-specific server between each frontend and the shared microservices:

graph TD
    Mobile[Mobile App] --> MBFF[Mobile BFF]
    Web[Web Dashboard] --> WBFF[Web BFF]

    MBFF --> UserSvc[User Service]
    MBFF --> CartSvc[Cart Service]

    WBFF --> UserSvc
    WBFF --> CartSvc
    WBFF --> RecSvc[Recommendation Service]
    WBFF --> NotifSvc[Notification Service]

Key properties:

  • Tight coupling by design: The Mobile BFF is owned by the mobile team. When the app screen changes, the BFF changes with it.
  • Data shaping: The BFF projects the microservice response to exactly what the client needs.
  • Protocol bridging: Microservices may speak gRPC internally. The BFF exposes REST or GraphQL to the client.

πŸ“Š Request Flow Through a BFF

The request lifecycle from client to microservices flows through the BFF:

flowchart TD
    A[Client Request] --> B{Which client?}
    B -->|Mobile| C[Mobile BFF]
    B -->|Web| D[Web BFF]
    C --> E[Auth Token Validation]
    D --> E
    E --> F[Parallel Upstream Calls]
    F --> G[User Service]
    F --> H[Cart Service]
    F --> I[Rec Service]
    G --> J[Response Aggregation]
    H --> J
    I --> J
    J --> K[Field Projection]
    K --> L[Client Response]

Each step adds value:

  1. Auth validation β€” the BFF verifies the token before hitting any upstream service.
  2. Parallel upstream calls β€” fan out to N services simultaneously to minimize latency.
  3. Response aggregation β€” merge the N responses into one JSON object.
  4. Field projection β€” strip fields the client doesn't use to reduce payload size.

πŸ”’ Deep Dive: What Lives Inside a BFF

A BFF is not a full microservice. It is a lightweight orchestration layer. Typical responsibilities:

ResponsibilityExample
Request aggregationCall User + Orders + Recs in parallel, merge response
Field projectionReturn only the 8 fields the mobile screen uses
Format translationConvert timestamps to "5 min ago" strings for the client
Auth token exchangeTrade internal service tokens for client-friendly JWTs
Protocol bridgingExpose REST to the client; call gRPC microservices internally
Client-specific cachingCache the mobile home feed; don't cache the admin dashboard

A BFF should contain no business logic. If you find yourself writing pricing rules or fraud detection inside a BFF, that code belongs in a separate service.


🧠 Deep Dive: Parallel Aggregation: The Common BFF Pattern

The most useful thing a BFF does is fan out requests and merge them:

# Mobile BFF: home screen endpoint
async def get_home_screen(user_id: str):
    user, cart, recommendations = await asyncio.gather(
        user_service.get(user_id),
        cart_service.get(user_id),
        rec_service.get_top5(user_id),
    )
    return {
        "name": user.first_name,
        "cart_count": len(cart.items),
        "recs": [r.title for r in recommendations],
    }

Three upstream calls run in parallel (~80 ms total instead of sequential ~240 ms). The client gets a single clean response in one round trip.


πŸ§ͺ Practical: Adding a New Mobile Screen

Suppose you add a "notifications center" screen to the mobile app. The workflow with a BFF:

Step 1 β€” Identify what the screen needs:

  • Unread notification count (from Notification Service)
  • Top 3 notification previews (from Notification Service)
  • User's preferred notification type (from User Service)

Step 2 β€” Write the BFF endpoint:

# Mobile BFF: notifications screen endpoint
async def get_notifications_screen(user_id: str):
    notifs, prefs = await asyncio.gather(
        notification_service.get_recent(user_id, limit=3),
        user_service.get_notification_prefs(user_id),
    )
    return {
        "unread_count": notifs.total_unread,
        "previews": [{"id": n.id, "text": n.text[:50]} for n in notifs.items],
        "sound_on": prefs.sound_enabled,
    }

Step 3 β€” The web BFF does NOT need to change. The web dashboard has its own endpoint for notifications with a different shape.

This is the BFF promise: frontend-driven API changes stay contained to that frontend's BFF. No negotiation between mobile and web teams about shared API shape.


🌍 Real-World Application: BFF in Production

  • Netflix: Each device type (TV, mobile, browser) has its own backend that shapes API responses to the specific screen and bandwidth constraints.
  • SoundCloud: Introduced BFFs to solve exactly the mobile/web mismatch β€” their blog post coined the pattern in 2015.
  • Spotify: Uses BFFs to support desktop, mobile, and embedded devices with different data densities.

πŸ“š What BFF Teaches You About API Design

BFF crystallizes several important API design principles:

  • Coupling intent matters. In most architectures, coupling is bad. In BFF, tight coupling between a BFF and its client is intentional β€” it's what makes the BFF responsive to frontend changes.
  • Separation of concerns at the interface layer. Business logic belongs in microservices. Presentation logic (field selection, format conversion) belongs in the BFF.
  • Team autonomy scales better than shared APIs. A shared API requires coordination every time any client changes. A BFF-per-team means changes can ship independently.
  • You can't optimize for everyone. If one API must serve mobile, web, and IoT devices, it will be suboptimal for all three. Specialization beats generalization when contexts diverge enough.

βš–οΈ Trade-offs & Failure Modes: Trade-offs, Failure Modes & Decision Guide

BFF is not always the right answer:

SituationBetter alternative
Single client type (e.g., web only)One clean REST/GraphQL API
Fewer than 3–4 microservicesDirect client calls are simpler
Small team that owns both frontend and backendGraphQL from a single service
Clients differ only in auth scopesAPI gateway with request transformation

Code duplication risk: If mobile and web BFFs share 80% of logic, refactor the common parts into shared service clients β€” not into both BFFs.


πŸ“Œ TLDR: Summary & Key Takeaways

  • BFF creates one dedicated API server per client type instead of one shared API for all.
  • Primary jobs: request aggregation, field projection, format translation, and protocol bridging.
  • BFF should contain no business logic β€” it is an orchestration layer, not a domain layer.
  • Use it when you have multiple clients with significantly different data needs.
  • Avoid it for single-client apps or when it would duplicate logic across BFFs.

πŸ“ Practice Quiz

  1. What is the core purpose of the Backend for Frontend (BFF) pattern?

    • A) To replace all microservices with a single monolithic backend.
    • B) To create a dedicated, client-specific API layer that shapes data for each frontend type.
    • C) To handle authentication and authorization for all services centrally.
    • D) To cache all API responses at the edge CDN. Correct Answer: B β€” A BFF is a thin orchestration layer tailored to one client type, responsible for request aggregation, field projection, and format translation β€” not business logic.
  2. Your mobile BFF and web BFF share 75% of their aggregation logic. What should you do?

    • A) Merge them into one shared BFF to remove duplication.
    • B) Extract shared logic into reusable service clients or an SDK used by both BFFs.
    • C) Move the shared logic into the microservices regardless of domain fit.
    • D) Accept the duplication; it's unavoidable with BFF. Correct Answer: B β€” When BFFs share significant logic, extract it into a shared library of service clients. Merging them defeats the purpose of client-specific backends.
  3. A pricing calculation needs to be added to your mobile BFF. What should you do?

    • A) Add it to the BFF since it's only needed on mobile.
    • B) Reject it β€” business logic belongs in a dedicated microservice, not in a BFF.
    • C) Add it to both the mobile and web BFF to keep them in sync.
    • D) Move it to the API gateway so all clients benefit. Correct Answer: B β€” BFFs should contain no business logic. Pricing rules are domain logic and belong in a service that all clients can share.
  4. When is BFF not the right pattern to use?

    • A) When you have both mobile and web clients with different data needs.
    • B) When you have a single client type and a simple, uniform API works fine.
    • C) When you want each frontend team to own its own backend layer.
    • D) When your microservices use different internal protocols. Correct Answer: B β€” BFF adds operational overhead. For single-client apps or very simple backends, it's unnecessary complexity.

πŸ› οΈ Spring Boot: Separate BFF Controllers for Web and Mobile Clients

Spring Boot is the standard Java framework for building REST services; its @RestController and reactive WebClient make it straightforward to create separate BFF controller classes β€” one per client type β€” that fan out to shared upstream microservice clients and project client-specific response DTOs.

The pattern: a MobileHomeController and a WebDashboardController live in separate Spring Boot modules (or applications), share the same service client beans, but expose different endpoints with different response shapes. Mono.zip fans out upstream calls in parallel β€” response latency equals the slowest upstream, not their sum.

// ─── Shared upstream client β€” injected into both BFF controllers ───────────
@Service
public class UserServiceClient {
    private final WebClient client;
    public UserServiceClient(WebClient.Builder builder) {
        this.client = builder.baseUrl("http://user-service").build();
    }
    public Mono<UserDto> getUser(String userId) {
        return client.get().uri("/users/{id}", userId)
            .retrieve().bodyToMono(UserDto.class);
    }
}

// ─── Mobile BFF β€” compact payload for small screens ──────────────────────
@RestController
@RequestMapping("/mobile/home")
@RequiredArgsConstructor
public class MobileHomeController {

    private final UserServiceClient  userClient;
    private final CartServiceClient  cartClient;

    @GetMapping("/{userId}")
    public Mono<MobileHomeResponse> getHomeScreen(@PathVariable String userId) {
        return Mono.zip(
            userClient.getUser(userId),
            cartClient.getCart(userId)
        ).map(t -> new MobileHomeResponse(
            t.getT1().getFirstName(),    // only first name β€” not the full profile
            t.getT2().getItemCount()     // only cart count β€” not the full cart detail
        ));
        // Parallel fan-out: ~80 ms total instead of sequential ~160 ms
    }
}

// ─── Web BFF β€” rich aggregated payload for the dashboard ─────────────────
@RestController
@RequestMapping("/web/dashboard")
@RequiredArgsConstructor
public class WebDashboardController {

    private final UserServiceClient           userClient;
    private final CartServiceClient           cartClient;
    private final RecommendationServiceClient recClient;
    private final NotificationServiceClient   notifClient;

    @GetMapping("/{userId}")
    public Mono<WebDashboardResponse> getDashboard(@PathVariable String userId) {
        return Mono.zip(
            userClient.getUser(userId),
            cartClient.getCart(userId),
            recClient.getTopRecommendations(userId, 10),
            notifClient.getUnreadCount(userId)
        ).map(t -> new WebDashboardResponse(
            t.getT1(),    // full user profile object
            t.getT2(),    // full cart with item list
            t.getT3(),    // 10 recommendation items
            t.getT4()     // unread notification count
        ));
    }
}

The Mobile BFF and Web BFF compile and deploy independently. Changing the mobile home screen layout β€” adding a cart_count field or removing recs β€” requires a change only to MobileHomeController, with zero negotiation with the web team.

Spring Cloud Gateway as the BFF aggregation layer is an alternative for teams that prefer configuration over code: SCG's AggregateResponseBody filter and request-transformation DSL can implement simple field projection without a dedicated controller class, though complex response shaping still benefits from explicit controller code.

For a full deep-dive on Spring Cloud Gateway aggregation filters and reactive BFF patterns with Spring WebFlux, a dedicated follow-up post is planned.


Abstract Algorithms

Written by

Abstract Algorithms

@abstractalgorithms