WebApp BFF (Backend-For-Frontend) Concept
Tags: frontend, typescript, architecture, javascript
Published: 2024-03-25 23:45:33

Hi there!!! It's nice to have you here again! How have you been? How you doing? Everything all right? I hope so!

In this article I want to introduce you de concept of BFF (Backend-For-Frontend). Let's create an application client and a server to understand, why this concept is so important that you know.

Imagine that your webapp needs some user datas, like posts, albums, todos, user info. Propabaly you will fetch this data using three requests for each service. Then your client is making 4 requests to add the datas and propably there's some informantions and some endpoint that you app don't need it.

Another problem, if you are doing 4 requests your app will need to wait for this 4 Promises wait to finish to render or format the data received.

Or if you have multiples devices apps, a web application, desktop and a mobile app. Probably the data that you will need it's going to be different on each case. Some data in your web applicaton, don't need in your mobile app.

But it major cases, we found the problem of performance. As I mentioned before, imagine that you have 4 ou 5 request to wait and after that continue with data manipulation.

Here is an example:

![Architeture without BFF](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w8mll9qzej8vdcwynior.png)

So let's create this example!

I will use Vite with React and Typescript for our web application.

I will create a directory called, bff-example:

```sh
mkdir bff-example
```

And here I will create both the webapp and the server (simulating the BFF).

First let's create the our webapp using:

```sh
yarn create vite webapp --template react-ts
```

I will enter on project created, install the dependencies (I will use yarn) and run and application

```sh
cd webapp
yarn
yarn dev
```

So here we are, now we have our web application:

![Vite Screen using React and Typescript](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mtyd0ptm821eol9iw8xk.png)

Now I will create the types:

`src/types.ts`
```ts
export interface DataResponse {
  user: User;
  albums: Album[];
  todos: Todo[];
  posts: Post[];
}

export interface Todo {
  userId: number;
  id: number;
  title: string;
  completed: boolean;
}

export interface User {
  id: number;
  name: string;
  username: string;
  email: string;
}

export interface Album {
  userId: number;
  id: number;
  title: string;
}

export interface Post {
  userId: number;
  id: number;
  title: string;
  body: string;
}
```

I will use the JSON placeholder to fake our services and API's. you can find: https://jsonplaceholder.typicode.com/

Now I will remove the App.css and index.css. And create the file `app.module.css` using CSS Modules.

`src/app.module.css`
```css
.container {
    width: 100vw;
    height: 100vh;
    display: flex;
    flex-direction: column;
  }
  .btn {
    padding: 4px;
    color: #fff;
    background-color: #1d4ed8;
    height: 2.8em;
    width: 8em;
    border-radius: 5px;
    cursor: pointer;
  }
  
  .btn:hover {
    background-color: #3b82f6;
  }
  
  .input {
    height: 1.8em;
    width: 20em;
  }
```

Now I will change the App.tsx to:

`src/App.tsx`
```tsx
import { useState } from "react";

import { DataResponse } from "./types";
import style from "./app.module.css";

function App() {
  const [data, setData] = useState<DataResponse>();
  const [id, setId] = useState<number>();

  const getData = async () => {};

  return (
    <main className={style.container}>
      <label htmlFor="userId"></label>
      <input
        className={style.input}
        type="number"
        name="userId"
        id="userId"
        onChange={(e) => setId(+e.target.value)}
      />
      <button className={style.btn} onClick={getData}>
        Fetch
      </button>
      <h1>Response:</h1>
      {data && <pre>{JSON.stringify(data, null, 4)}</pre>}
    </main>
  );
}

export default App;
```

Now we have this screen:

![New screeen with an input and a button to fetch](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fggl0bqiph6ry3eupp0f.png)

Sorry, I need some styles (LoL). But notice we have an input to insert the user ID and a button to fetch the data when clicked.

Now in `getData` function let's call the endpoints:

```tsx
const BASE_URL = "https://jsonplaceholder.typicode.com";

const getData = async () => {
    const userData = await fetch(`${BASE_URL}/users/${id}`);
    const user: User = await userData.json();

    const postsData = await fetch(`${BASE_URL}/posts?userId=${user.id}`);
    const posts: Post[] = await postsData.json();

    const todosData = await fetch(`${BASE_URL}/todos?userId=${user.id}`);
    const todos: Todo[] = await todosData.json();

    const albumsData = await fetch(`${BASE_URL}/albums?userId=${user.id}`);
    const albums: Album[] = await albumsData.json();

    const formattedResponse = { user, posts, todos, albums };
    setData(formattedResponse);
  };
```

Wooww, and now you can see the problem, isn’t? We are making 5 requests on the client side. We need to wait this five requests, to after that, format and render on screen.

![Response of requests](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tnz1japvax5wnarft0bo.png)


## BFF Concept

Now let's create a server using NestJS with Node, to simulate our BFF:
On directory `bff-example`, let's create our project:

```sh
npx @nestjs/cli new bff-server
```

Now I will create the types, similiar on frontend:

`src/types.ts`
```ts
export interface DataResponse {
  user: User;
  albums: Album[];
  todos: Todo[];
  posts: Post[];
}

export interface ErrorDataResponse {
  statusCode: number;
  status: number;
  message: string;
}

export interface Todo {
  userId: number;
  id: number;
  title: string;
  completed: boolean;
}

export interface User {
  id: number;
  name: string;
  username: string;
  email: string;
}

export interface Album {
  userId: number;
  id: number;
  title: string;
}

export interface Post {
  userId: number;
  id: number;
  title: string;
  body: string;
}
```

I will create the endpoint of GET type and receive the user id as param:

`src/app/app.controller.ts`
```ts
import { Controller, Get, Param } from '@nestjs/common';

import { AppService } from './app.service';

@Controller('data')
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get(':id')
  getData(@Param('id') id: string) {
    return this.appService.getData(id);
  }
}
```

Now on our Service, let's change to call the endpoints that was on client here:

`src/app/app.service.ts`
```ts
import { Injectable } from '@nestjs/common';
import {
  Album,
  DataResponse,
  ErrorDataResponse,
  Post,
  Todo,
  User,
} from '../type';

const BASE_URL = 'https://jsonplaceholder.typicode.com';

@Injectable()
export class AppService {
  async getData(id: string): Promise<DataResponse | ErrorDataResponse> {
    try {
      const userData = await fetch(`${BASE_URL}/users/${id}`);
      const user: User = await userData.json();

      const postsData = await fetch(`${BASE_URL}/posts?userId=${user.id}`);
      const posts: Post[] = await postsData.json();

      const todosData = await fetch(`${BASE_URL}/todos?userId=${user.id}`);
      const todos: Todo[] = await todosData.json();

      const albumsData = await fetch(`${BASE_URL}/albums?userId=${user.id}`);
      const albums: Album[] = await albumsData.json();

      return { user, posts, todos, albums };
    } catch (error) {
      return { status: 500, message: 'Error', statusCode: 1000 };
    }
  }
}
```

Now let's start our application using:

```sh
yarn start
```

Now if we access the endpoint: `http://localhost:3000/data/1`

We will receive the same data:

![Server Response](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cegmqgmple1wibc4nd3g.png)

Amazing, isn’t? Now the responsability of the requests it will be made by the server. So now the client will not be overloaded or have performance problems. The BFF and the another services can scale (using Docker and Kubernetes) to respond for our web application.

Now let's go back to our web app and change the `getData` function:

```tsx
const getData = async () => {
    // Here we can see an example of BFF
    // We can call the four calls here, but it was made on BFF service
    // leaving the frontend call only one endpoint
    const response = await fetch(`http://localhost:3000/data/${id}`);
    const data: DataResponse = await response.json();
    setData(data);
  };
```

And let's see the same result:

![With BFF](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y743nb97bhy0uhyk9r13.png)

But now the BFF is responsible to fetch the data and manipulate for our clients (web application, desktop, mobile app).


![Architeture with BFF](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v85fcgt7bnfryl6oregy.png)


I want to thank Adriana Saty for giving me the idea to create this article.
Saty Linkedin: https://www.linkedin.com/in/adriana-saty/


And that's it, people!
I hope you liked it.
Thank you so much and stay well, always!!!


![Thank you gif](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wc8z242na2xu8ftfg889.gif)

Contacts:
Linkedin: https://www.linkedin.com/in/kevin-uehara/
Instagram: https://www.instagram.com/uehara_kevin/
Twitter: https://twitter.com/ueharaDev
Github: https://github.com/kevinuehara
dev.to: https://dev.to/kevin-uehara
Youtube: https://www.youtube.com/@ueharakevin/