Backend for Frontend

The modern web application is increasingly complex. We’re moving away from monolithic architectures towards microservices, leading to a proliferation of backend services. This presents a challenge: how do we efficiently expose this granular backend to our various frontend applications (web, mobile, IoT, etc.) without overwhelming them with unnecessary data or complexity? The answer, often, is a Backend for Frontend (BFF).

What is a Backend for Frontend (BFF)?

A BFF is a server-side application specifically designed to cater to the needs of a particular frontend client. Instead of having one monolithic backend exposing all its data and functionality to every client, a BFF acts as an intermediary, aggregating and transforming data from multiple backend microservices into a format optimized for the consuming frontend.

Think of it as a translator. The frontend speaks a specific language (e.g., requires certain data fields, uses a specific API format), and the BFF translates the frontend’s requests into calls to the relevant backend microservices, then translates the responses back into a format easily digestible by the frontend.

This approach offers many significant advantages:

Architectural Diagram

Here’s a simple illustration using a Diagram showing a BFF in action:

graph LR
    A[Web Frontend] --> B(BFF for Web);
    C[Mobile Frontend] --> D(BFF for Mobile);
    B --> E[User Service];
    B --> F[Product Service];
    D --> E;
    D --> G[Inventory Service];

In this example:

Example: A Node.js BFF

Let’s consider a simple Node.js BFF that aggregates data from two backend microservices: a userService and a productService. The frontend needs a user’s name and their purchased products.

First, we’ll assume our backend microservices expose these endpoints:

userService:

productService:

Here’s a simplified Node.js BFF using Express.js:

const express = require('express');
const axios = require('axios');
const app = express();
const port = 3000;

app.get('/user/:id', async (req, res) => {
  try {
    const userId = req.params.id;
    const userResponse = await axios.get(`http://user-service/users/${userId}`);
    const productResponse = await axios.get(`http://product-service/products/user/${userId}`);

    const userData = userResponse.data;
    const products = productResponse.data;

    res.json({
      name: userData.name,
      products: products,
    });
  } catch (error) {
    console.error(error);
    res.status(500).send('Error fetching data');
  }
});

app.listen(port, () => {
  console.log(`BFF listening on port ${port}`);
});

This BFF fetches user data and product data from separate services and combines them into a single response suitable for the frontend. Error handling is important in a BFF to ensure resilience.

BFF vs. API Gateway

It’s important to differentiate between a BFF and an API gateway. While both act as intermediaries, they have distinct purposes:

In many architectures, both BFFs and API gateways coexist, with BFFs sitting behind API gateways for added security and management.

When to Use a BFF

Consider implementing a BFF when: