TypeScript – Wrapping axios with an HttpClient class


Motivation

We often start using third-arty libraries like axios directly in our code. There is nothing wrong about this. However, in a world of ever changing libraries, packages, versions, etc. using these libraries API directly might lead to inconsistent code.

A good practice is to create your own abstraction and wrap the calls to the library API into your wrapper. This will allow you to keep code more consistent, and also more easily switch to a different library/package in the future if you will have to. This is because you wrap all the calls to the third-party library in one place and as long as your wrapper interface does not change, you will be able to just replace the implementation of your wrapper methods to switch to the new library.

Let’s Code!

In the specific case of code used to make Http request, we can create a an interface called IHttpClient and then a class called HttpClient that will implement such an interface. Inside our HttpClient methods we will invoke axios methods.

View Post

For now, let’s just pretend our HttpClient will have only a generic get<T> and post<T> methods:

export interface IHttpClient {
  get<T>(parameters: IHttpClientRequestParameters): Promise<T>
  post<T>(parameters: IHttpClientRequestParameters): Promise<T>
}

We will need also an interface to pass our parameters to the get<T> and post<T> with one argument to avoid code cluttering. We’ll call this IHttpClientRequestParameters and it will take some a generic T to define the type of the payload passed in (if any) and additional common parameters that are used for any kind of Http request:

export interface IHttpClientRequestParameters<T> {
  url: string
  requiresToken: boolean
  payload?: T
}

Here is the description in details of each property of our request parameters interface:

  • url: this is the full url of the API endpoint to which we need to make the request (it will have to include the query string parameters)
  • requiresToken: this is a boolean that indicates if our request will have to also add an authentication token (i.e. Jwt token)
  • payload: this is the payload for the POST request so it is optional.

Now we can code our HttpClient class implementing our IHttpClient interface and using axios:. Let’s start by importing the things we need from axios and write the initial declaration of our HttpClient class without any implementation yet:

import axios, { AxiosRequestConfig, AxiosError, AxiosResponse } from 'axios'

export class HttpClient implements IHttpClient {
  // ... implementation code will go here
}

Let’s now add the implementation of the get<T> method as defined in our interface. Notice here I am using Promise syntax, but you are welcome to just use async/await syntax. Our get<T> method will take an instance of IHttpClientRequestParameters and return a Promise<T>:

get<T>(parameters: IHttpClientRequestParameters): Promise<T> {
  return new Promise<T>((resolve, reject) => {
    // extract the individual parameters
    const { url, requiresToken } = parameters 

    // axios request options like headers etc
    const options: AxiosRequestConfig = {
      headers: {}
    }

    // if API endpoint requires a token, we'll need to add a way to add this.
    if (requiresToken) {
      const token = this.getToken()
      options.headers.RequestVerificationToken = token
    }

    // finally execute the GET request with axios:
    axios
      .get(url, options)
      .then((response: any) => {
        resolve(response.data as T)
      })
      .catch((response: any) => {
        reject(response)
      })
   
  })
}

Similarly, now add the implementation of the post<T> method as defined in our interface. Like get<t>, our post<T> method will take an instance of IHttpClientRequestParameters and return a Promise<T>:

post<T>(parameters: IHttpClientRequestParameters): Promise<T> {
  return new Promise<T>((resolve, reject) => {
    const { url, payload, requiresToken } = parameters

    // axios request options like headers etc
    const options: AxiosRequestConfig = {
      headers: {}
    }

    // if API endpoint requires a token, we'll need to add a way to add this.
    if (requiresToken) {
      const token = this.getToken()
      options.headers.RequestVerificationToken = token
    }

    // finally execute the GET request with axios:
    axios
      .post(url, payload, options)
      .then((response: any) => {
        resolve(response.data as T)
      })
      .catch((response: any) => {
        reject(response)
      })
  })
}

Now you can export an instance of the HttpClient and use throughout your code:

export const httpClient = new HttpClient()

Here is an example where we consume it to fetch a list of “items” of type IItem:

fetchItems(): Promise<IItem[]> {
  // prepare our request parameters
  const getParameters: IHttpClientPostParameters = {
    url: 'http://yourapiurl/items',
    requiresToken: false
  }

  // just return httpClient.get (which is a promise) or again use async/await if you prefer
  return httpClient.get<IItem[]>(getParameters)
}

Conclusion

You now have an HttpClient that abstract the different Http request like get or post etc and encapsulate the code in one place. Within the HttpClient you use axios, but later might switch to another library for any reason in the future, and you are not polluting your code with axios methods.

You can find this also on Medium if you prefer and can book mark it there: https://medium.com/vue-typescript/wrapping-axios-within-the-httpclient-class-using-typescript-24ccfb0ccec