Previous API Gateway Service-Discovery-and-health-Check Next

The Backend for Frontend (BFF) Pattern

The Backend for Frontend (BFF) Pattern

The Backend for Frontend (BFF) pattern is an architectural design where a dedicated backend service is created for each type of frontend client, such as a web application, mobile app, or third-party service. This contrasts with a single, general-purpose API that serves all clients, leading to a tailored and optimized experience for each. The BFF service acts as a mediator, aggregating, transforming, and filtering data from underlying microservices to meet the precise needs of its specific frontend.

BFF Pattern

🧩 What Is the BFF Pattern?

The BFF pattern introduces a dedicated backend layer for each frontend type. Instead of having a single generic API for all clients, each frontend gets its own backend that:

  • Aggregates data from multiple microservices
  • Transforms it into a format optimized for that frontend
  • Handles client-specific logic and performance needs

Think of it as a personal assistant for each frontendβ€”filtering, formatting, and simplifying the backend complexity.

πŸ“± Example Scenario

Imagine you have:

  • A Web App that needs rich, detailed data
  • A Mobile App that needs lightweight, fast responses

Instead of forcing both to use the same API, you create:

  • WebBFF that aggregates and formats data for desktop browsers
  • MobileBFF that compresses payloads and minimizes round trips

Each BFF communicates with the same set of microservices but tailors the experience for its client.

Example: BFF with .NET Core

Imagine an e-commerce platform with two main clients: a web browser application and a mobile app. Both need to display a user's profile and recent order history. However, they have different data and performance requirements:

The Web App has more screen space and needs detailed user information and a comprehensive list of orders with full item details.

The Mobile App requires a lightweight, fast-loading interface with only essential user information and a limited summary of recent orders.

A single, generic backend API would either have to over-fetch data for the mobile client (sending more data than needed) or require complex filtering logic on the client side, leading to inefficiency and slower performance.

Instead, we can implement the BFF pattern by creating a dedicated .NET Core backend for each client.

1. Core Microservices

First, let's assume we have two core microservices that provide generic data.

User Service (Controllers/UserController.cs)

[ApiController]
[Route("api/[controller]")]
public class UserController : ControllerBase
{
    [HttpGet("{id}")]
    public IActionResult GetUser(string id)
    {
        // Imagine fetching from a database
        return Ok(new {
            Id = id,
            FirstName = "Jane",
            LastName = "Doe",
            Email = "jane.doe@example.com",
            Phone = "555-1234",
            FullAddress = "123 Main St, Anytown, USA"
        });
    }
}

Order Service (Controllers/OrderController.cs)

[ApiController]
[Route("api/[controller]")]
public class OrderController : ControllerBase
{
    [HttpGet("user/{userId}")]
    public IActionResult GetOrders(string userId)
    {
        // Return a full list of orders for a user
        return Ok(new[] {
            new { OrderId = 1, OrderDate = DateTime.Now, Total = 59.99, ItemCount = 2, Items = new[] { /* ...detailed items... */ } },
            new { OrderId = 2, OrderDate = DateTime.Now.AddDays(-1), Total = 12.50, ItemCount = 1, Items = new[] { /* ...detailed items... */ } }
        });
    }
}

2. Implement the BFFs

Next, we create a separate ASP.NET Core API project for each frontend. These are the BFFs. They will use HttpClient to communicate with the core microservices.

Web App BFF Project (Controllers/WebBffController.cs)
This BFF aggregates data for the desktop web UI.

[ApiController]
[Route("api/[controller]")]
public class WebBffController : ControllerBase
{
    private readonly HttpClient _httpClient;

    public WebBffController(HttpClient httpClient)
    {
        _httpClient = httpClient;
        _httpClient.BaseAddress = new Uri("http://localhost:5001/"); // Base URL for core microservices
    }

    [HttpGet("user-details/{userId}")]
    public async Task<IActionResult> GetUserDetailsForWeb(string userId)
    {
        // Aggregate calls to both microservices
        var userTask = _httpClient.GetFromJsonAsync<object>($"/api/user/{userId}");
        var ordersTask = _httpClient.GetFromJsonAsync<object[]>($"/api/order/user/{userId}");

        await Task.WhenAll(userTask, ordersTask);

        var response = new {
            User = userTask.Result,
            Orders = ordersTask.Result
        };

        return Ok(response);
    }
}

Mobile App BFF Project (Controllers/MobileBffController.cs)
This BFF aggregates and transforms data to be lightweight for the mobile UI.

[ApiController]
[Route("api/[controller]")]
public class MobileBffController : ControllerBase
{
    private readonly HttpClient _httpClient;

    public MobileBffController(HttpClient httpClient)
    {
        _httpClient = httpClient;
        _httpClient.BaseAddress = new Uri("http://localhost:5001/"); // Base URL for core microservices
    }

    [HttpGet("user-details/{userId}")]
    public async Task<IActionResult> GetUserDetailsForMobile(string userId)
    {
        // Aggregate calls to microservices and transform the data
        var user = await _httpClient.GetFromJsonAsync<JObject>($"/api/user/{userId}");
        var orders = await _httpClient.GetFromJsonAsync<JArray>($"/api/order/user/{userId}");

        // Create a lightweight response for mobile
        var mobileResponse = new {
            FirstName = user["firstName"],
            RecentOrders = orders.Select(o => new {
                OrderId = o["orderId"],
                Total = o["total"]
            }).Take(3) // Only show the three most recent orders
        };

        return Ok(mobileResponse);
    }
}

How this example works

A request from the web app goes to WebBffController. This controller calls the core User and Order services, aggregates the full data, and returns it.

A request from the mobile app goes to MobileBffController. This controller calls the same core services but then filters the user data and limits the order history before sending a lightweight response, preventing over-fetching.

The core microservices remain generic and unaware of the different clients. Any changes to the UI-specific requirements only affect the corresponding BFF, not the core services or other BFFs.

βœ… Advantages

  • 🎯 Tailored APIs: Each client gets an API specific to its needs.
  • 🧠 Simplified frontend: BFF handles aggregation and transformation, simplifying the frontend.
  • πŸš€ Independent development: BFFs can be developed and deployed independently.
  • πŸ”’ Improved security: Sensitive operations handled server-side.
  • ⚑ Optimal performance: Reduced chattiness and optimized payloads improve speed.
  • 🎯 Client-Specific Optimization: Tailor APIs to frontend needs (e.g., reduce payload size for mobile)
  • πŸ”’ Security Isolation: Apply client-specific auth and rate limits
  • 🧠 Simplified Frontend Logic: Offload complex orchestration to the BFF
  • πŸ”„ Faster Iteration: Frontend teams can evolve independently
  • 🧩 Better Separation of Concerns: Keeps microservices clean and focused

⚠️ Disadvantages

  • βš™οΈ Increased complexity in managing multiple services.
  • πŸ“‹ Possible code duplication between BFFs.
  • πŸ”§ Maintenance overhead due to dependency changes.
  • 🐒 Slight added latency from the extra layer.

πŸ• When to use

  • 🌍 Diverse clients like web, mobile, and desktop.
  • πŸ“Š Complex data aggregation from multiple sources.
  • 🧩 Legacy modernization using the Strangler pattern.
  • πŸ” Enhanced security needs.

🚫 When Not to Use

  • 🧱 Simple Applications: If you only have one frontend, a BFF adds unnecessary complexity
  • πŸ§‘β€πŸ’» Small Teams: Maintaining multiple BFFs can be overhead
  • 🐒 Latency-Sensitive Systems: Extra hops may introduce delay

πŸ› οΈ Best Practices

  • 🧼 Keep BFFs Thin: Focus on orchestration, not business logic
  • πŸ” Secure Each BFF Independently: Use JWT, OAuth2, etc.
  • πŸ§ͺ Test End-to-End: Ensure BFFs handle edge cases gracefully
  • πŸ“¦ Use Caching Strategically: Reduce load and improve speed
  • 🧬 Version APIs Carefully: Avoid breaking changes across clients
  • πŸ“Š Monitor Separately: Track performance and errors per BFF
  • πŸͺΆ Start simple and evolve gradually.
  • πŸ” Use IHttpClientFactory with Polly for resilience.
  • πŸ“± Design DTOs with mobile clients in mind.
  • 🧠 Leverage caching for performance.
  • πŸ“¦ Use shared libraries to reduce duplication.

⚠️ Precautions

  • 🧯 Avoid Duplication: Don’t replicate logic across BFFs
  • 🚫 Avoid creating a β€œBFF monolith.”
  • 🧭 Monitor and log BFF performance and health.
  • πŸ”’ Use clear API versioning for stability.
  • βš™οΈ Handle errors gracefully with proper fallbacks.
  • 🧱 Don’t Overload BFFs: They’re not meant to replace microservices
  • πŸ”„ Manage Consistency: Ensure data sync across BFFs
  • 🧭 Plan for Scale: BFFs can become bottlenecks if not designed well
Back to Index
Previous API Gateway Service-Discovery-and-health-Check Next
*