Imagine a scenario where the backend of a project is spread across various microservices. For instance, to load a page displaying user information along with their notifications and a data table, we would need to make requests to different URLs while ensuring that these requests are sent in the correct order. This process can be quite labor-intensive. But what if there were a solution to this problem?
This is where BFF (Backend For Frontend) comes into play. BFF is an architectural pattern that involves creating a separate server layer that acts as a proxy server. It handles sending requests to the necessary services and transforms the responses into the format needed by the frontend.
A fitting analogy for BFF is a waiter. When we go to a restaurant, instead of running to each chef for our orders (making requests to different services), we interact with the waiter (BFF). We provide the waiter with what we need (a single request to BFF), and they relay that information to the necessary chefs, gather all the orders, and bring them to our table.
In this article, we will explore BFF and its intricacies.
What is BFF? Core Concepts
We can conclude that BFF serves as a personal backend for our frontend.
The main idea behind this pattern is that each client, whether it’s a web application or a mobile app, has its own “companion server” that:
- Understands its specific requirements (e.g., a mobile app might need a response with fewer fields than a web app).
- Optimizes interactions with the server side of the application.
- Provides a specialized API for a specific UI.
It’s important to understand that BFF is an architectural pattern that establishes the connection between the client and the server, rather than a framework or library.
Key Characteristics of BFF:
- Individuality: BFF is created for a specific client and is tailored to:
- The specifics of data presentation on the client.
- The nature of network interactions.
- Specific data format requirements.
- Optimal data caching strategies.
- “Thin” Network Layer: BFF should not contain business logic, which is crucial. Its main functions include:
- Data aggregation—collecting necessary data from different services into a unified model.
- Data transformation for client needs.
- Request routing to the necessary services.
- Error handling.
The use of this pattern is becoming common among frontend communities due to:
- Increasing adoption of microservice architecture in projects.
- A wide variety of client devices.
BFF Architecture in Detail
Let’s look at how BFF works:
Request Flow through BFF:
- Client request—frontend sends a single request to BFF.
- Data enters the layer, triggering the BFF architecture.
- Authentication—verifying tokens and parameters.
- Parallel requests—accessing the necessary microservices.
- Data aggregation—combining results from microservice requests.
- Data transformation—converting data into the required format for the client.
- Client response—sending the processed response back to the frontend.
BFF consists of several logical components:
- Router: Determines which handler should process the request and validates the data.
// Example routing with Express.js
app.get('/web/products/:id', productPageHandler);
app.get('/web/user-profile', userProfileHandler);
- Data Aggregator: Manages parallel requests and combines their results.
class DataAggregator {
async aggregateProductPageData(productId) {
const [product, reviews, recommendations, stock] = await Promise.all([
this.catalogService.getProduct(productId),
this.reviewService.getReviews(productId),
this.recommendationService.getSimilar(productId),
this.inventoryService.getStockInfo(productId)
]);
return { product, reviews, recommendations, stock };
}
}
- Transformers: Convert data into the format needed by the client.
class ProductTransformer {
static transformForWeb(productData) {
const { product, reviews, recommendations, stock } = productData;
return {
productInfo: {