17 Aug 2022 · Software Engineering

    Why You Need an API Layer and How To Build It in React

    11 min read
    Contents

    Data is generally retrieved and sent to servers via an API. This means that your frontend project is likely to make many API calls. Calling an API takes several lines of code and spreading this logic throughout your application leads to code duplication, making your codebase less maintainable. Also, if the endpoint of an API changes, you need to update the code at all points where it is used.

    Now, imagine adding a layer to your architecture that encapsulates everything required to call all the APIs you need. Having all the logic related to API calls in one place offers several advantages and allows you to avoid the aforementioned drawbacks. Specifically, an API layer avoids code duplication and makes your codebase more maintainable.This is what an API layer is about.

    Let’s dig deeper into what an API layer is and how to implement one. The code examples in this article will be in React, but keep in mind that this approach can be easily applied with any JavaScript framework.

    Follow this tutorial and learn how to achieve the following result:

    What is an API layer?

    An API layer is the part of an Application that is responsible for Programming everything it needs to send and receive data via an Interface (APIs). In other words, all the external calls an application performs go through this layer.

    All the files needed to implement this architectural layer are generally saved in the apis folder. In a React project, you can organize the files your API layer consists of based on the following naming pattern:

    <endpointType>API.js

    Each <endpointType> describes the APIs you can find in that file. Keep in mind that each file within the API layer folder should contain all APIs that have a similar purpose or are related to the same data entity. Also, an API layer generally involves some configurations and utilities.

    This is what a sample API layer in a React application may look like:

    apis
    ├── configs
    │    ├── axiosConfig.js
    │    └── axiosUtils.js
    │
    ├── AuthorAPI.js
    .
    .
    .
    ├── ProductAPI.js
    .
    .
    .
    └── UserAPI.js

    This is just an example, and you can define your API Layer structure as you see fit. On the other hand, being consistent with naming conventions makes it easier to find APIs.

    Now, you can call an API as follows:

    import React, { useEffect, useState } from "react"
    import { ProductAPI } from "src/apis/ProductAPI"
    
    function ProductListComponent(props) {
      const [products, setProducts] = useState([])
    
      useEffect(() => {
        ProductAPI.getAll().then((products) => {
          // response handling
          setProducts(products)
        })
      }, [])
    
      // presenting the product list ...
      return <div>...</div>
    }
    
    export default ProductListComponent

    What is important to notice here is that the API to retrieve the list of all products is called when the then() function is invoked, not on ProductAPI.getAll().

    This means that the functions exposed by the API layer only encapsulate what is needed to call the API, but they do not perform any API calls. In particular, the API function returns a Promise. If you are not familiar with this, a Promise is a proxy for a value not known at creation time, which will be returned in the future.

    In detail, functions defined in the API layer immediately return a Promise. Then, you can use that Promise to wait for the API to respond and define what to do with the response data with the callback function passed to then().

    Therefore, the API layer allows you to separate where the API calls are defined and where they are actually performed. This is made possible by the Promise technology. Consequently, you need a Promise-based HTTP client like axios to implement an API layer. This is the only hard requirement for implementing an API layer.

    Let’s now delve into the benefits that an API layer can bring to your architecture.

    Why your architecture needs an API layer

    There are several reasons why you should adopt an API layer in your frontend architecture. Let’s focus on the three most important ones.

    1. Avoiding code duplication

    Modern frontend applications rely on APIs to retrieve and write data. Also, your application is likely to call the same API in different places. With a traditional frontend architecture, this means repeating the following logic required to call the API each time:

    // the logic required to call the API to retrieve the list of all products
    // with the axios HTTP client
    axios.request({
        method: "GET",
        url: `/api/v1/products/`
    })

    Basically, you have to write these exact four lines of code each time you want to retrieve the list of all products via API. This involves code duplication, which should be avoided because it makes your application bulky, less quality-oriented, and increases the risk of spreading an error or security problem throughout the codebase.

    In contrast, by using an API layer you can encapsulate the logic needed to call the API in functions and consequently avoid duplication of code. Instead of repeating the same logic over and over again, you simply have to call a function.

    2. Centralizing API requests in the same place

    An API layer lets you centralize all the logic needed to call APIs in the same place. Now, if an API endpoint changes, you only need to change the one function definition in the API layer. Without this, you would have to update each API call made throughout the application.

    Also, an API layer centralizes the configuration of the HTTP client in the configs sub-folder. This means that if you suddenly have to pass new header or cookie values to your backend in all requests, you only have to change the HTTP client configuration files accordingly:

    // src/apis/configs/axiosConfigs.js
    
    import axios from 'axios';
    
    // initializing the axios instance with custom configs
    const api = axios.create({
       withCredentials: true,
      // adding a custom language header   
      headers: {
        "Custom-Language": "en",
      },
    });
    
    export default api;

    As you can see, adding a new header value only takes a few lines of code. In a traditional scenario, you might have to update every single API call throughout the codebase. Therefore, an API layer also makes applications easier to maintain and evolve.

    3. Easily handle request cancellation

    Thanks to the centralized nature of the API layer, you can effortlessly add custom features like cancellation to all API request definitions.

    Your frontend application performs API calls asynchronously. This means that when calling the same API twice in a short amount of time, the response to the first request may arrive after the response to the second one. Because of this, your application may render inconsistent data.

    A popular approach to deal with this problem is the debounce technique. The idea behind debounce is to wait a certain amount of time before executing the same operation twice. Implementing this and finding the right debounce time value for each scenario, however, is not easy. For this reason, you should consider API request cancellation. With request cancellation, the new API request simply cancels the previous one of the same type.

    API request cancellation is simpler than debouncing and more useful. As you are about to see, adding such a feature to an API layer is easy and only requires a few lines of code. Considering that you might need request cancellation only in specific circumstances, the functions in your API layers should always pass a cancel boolean parameter to enable it as follows:

    function getProducts(cancel = false) {
       // ...
    } 

    Implement an API Layer in React with axios

    Now that you know what an API layer is and why it is important, let’s build one in React. Do not forget that this approach can be easily implemented using any other JavaScript framework.

    Prerequisites

    As explained earlier, an API layer only requires a Promise-based HTTP client. Here you will see how to implement an API layer with axios, one of the most popular Promise-based HTTP clients in JavaScript. Keep in mind that any other Promise-based HTTP client will do. Similarly, you can also use the JavaScript Fetch API or the superagent library.

    You can add axios to your project’s dependencies as follows:

    npm install axios

    Defining the API layer with cancellation

    Let’s see how to define the API layer step by step. First, let’s start with the configuration files.

    This is what a configs/axiosConfigs.js may look like:

    // src/apis/configs/axiosConfigs.js
    
    import axios from "axios"
    
    export const api = axios.create({
      withCredentials: true,
      baseURL: "https://yourdomain.com/api/v1",
    })
    
    // defining a custom error handler for all APIs
    const errorHandler = (error) => {
      const statusCode = error.response?.status
    
      // logging only errors that are not 401
      if (statusCode && statusCode !== 401) {
        console.error(error)
      }
    
      return Promise.reject(error)
    }
    
    // registering the custom error handler to the
    // "api" axios instance
    api.interceptors.response.use(undefined, (error) => {
      return errorHandler(error)
    })

    Here, you learned how to register a custom error handler function thanks to axios interceptors. This is just an example, and you can place any kind of custom behavior you want your axios instance to have in this file.

    Now, let’s implement the logic required to implement API request cancellation in the configs/axiosUtils.js file.

    // src/apis/configs/axiosUtils.js
    
    export function defineCancelApiObject(apiObject) {
        // an object that will contain a cancellation handler
        // associated to each API property name in the apiObject API object
        const cancelApiObject = {}
    
        // each property in the apiObject API layer object
        // is associated with a function that defines an API call
    
        // this loop iterates over each API property name
        Object.getOwnPropertyNames(apiObject).forEach((apiPropertyName) => {
            const cancellationControllerObject = {
                controller: undefined,
            }
    
            // associating the request cancellation handler with the API property name
            cancelApiObject[apiPropertyName] = {
                handleRequestCancellation: () => {
                    // if the controller already exists,
                    // canceling the request
                    if (cancellationControllerObject.controller) {
                        // canceling the request and returning this custom message
                        cancellationControllerObject.controller.abort()
                    }
    
                    // generating a new controller
                    // with the AbortController factory
                    cancellationControllerObject.controller = new AbortController()
    
                    return cancellationControllerObject.controller
                },
            }
        })
    
        return cancelApiObject
    }

    Since version 0.22.0, axios has supported the JavaScript AbortController to cancel requests. The snippet above defines a cancelApiObject that has properties with the same names as those of each apiObject. Each of the cancelApiObject properties is associated with a handleRequestCancellation() function that is responsible for defining the cancellation logic of the previous request.

    Let’s now see how to define an API layer file and how to use the cancellation utility defined above:

    // src/apis/productAPI.js
    
    import { api } from "./configs/axiosConfigs"
    import { defineCancelApiObject } from "./configs/axiosUtils"
    
    export const ProductAPI = {
      get: async function (id, cancel = false) {
        const response = await api.request({
          url: `/products/:id`,
          method: "GET",
          // retrieving the signal value by using the property name
          signal: cancel ? cancelApiObject[this.get.name].handleRequestCancellation().signal : undefined,
        })
    
        // returning the product returned by the API
        return response.data.product
      },
      getAll: async function (cancel = false) {
        const response = await api.request({
          url: "/products/",
          method: "GET",
          signal: cancel ? cancelApiObject[this.getAll.name].handleRequestCancellation().signal : undefined,
        })
    
        return response.data.products
      },
      search: async function (name, cancel = false) {
        const response = await api.request({
          url: "/products/search",
          method: "GET",
          params: {
            name: name,
          },
          signal: cancel ? cancelApiObject[this.search.name].handleRequestCancellation().signal : undefined,
        })
    
        return response.data.products
      },
      create: async function (product, cancel = false) {
        await api.request({
          url: `/products`,
          method: "POST",
          data: product,
          signal: cancel ? cancelApiObject[this.create.name].handleRequestCancellation().signal : undefined,
        })
      },
    }
    
    // defining the cancel API object for ProductAPI
    const cancelApiObject = defineCancelApiObject(ProductAPI)

    This is what an API layer file looks like. As you can see, it is easy to define and the cancellation logic only takes a couple of lines of code. This is just one API file, and your API layer can have as many files as you need.

    API Layer in action

    Let’s now see the API layer in action with a React demo project based on the free and open PokeAPI project.

    Clone the GitHub repository of the demo project and launch it locally with the following commands:

    git clone https://github.com/Tonel/api-layer-example-semaphore
    cd api-layer-example-semaphore
    npm install
    npm start

    You will get the same result offered by the live demo from the beginning of this article!

    Conclusion

    In this article, you learned what an API layer is, some of the many benefits it can provide to your architecture, and how to implement it in React. An API layer is a portion of your architecture that exposes everything your application needs to send and receive data via API calls. It centralizes API logic and consequently allows you to avoid code duplication. Implementing an API layer in JavaScript is not complex, and it only requires a Promise-based HTTP client. 

    Congratulations! You have now learned how to build an API layer in React and have seen it in action via a sample demo.

    6 thoughts on “Why You Need an API Layer and How To Build It in React

    Leave a Reply

    Your email address will not be published. Required fields are marked *

    Avatar
    Writen by:
    I'm a software engineer, but I prefer to call myself a Technology Bishop. Spreading knowledge through writing is my mission.
    Avatar
    Reviewed by:
    I picked up most of my soft/hardware troubleshooting skills in the US Army. A decade of Java development drove me to operations, scaling infrastructure to cope with the thundering herd. Engineering coach and CTO of Teleclinic.