Make fetch better and your API request methods easier to implement
Tags: webdev, javascript, typescript, node
Published: 2020-10-11 21:11:32

In this post, I will share my thought on how I like to structure methods on how to get data from your REST API backend. The idea here is to show the basics of my idea, and then you should add, remove, and adopt the code to your specific needs.
 
This method will also be useful in what ever framework or platform you are in like React, Angular, Vue, or even NodeJS (with a few polyfills, for example fetch...)!

Oh, and a little heads up. We are going object-oriented programming. So a basic understanding of [javascript classes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes) and the [fetch api](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) would be good before you keep going.

## The end game
In the end, we will be able to request data from our backend like this to assign a users variable:
```javascript
users = await backend.users.get()
```
Instead of something like this:
```javascript
const res = await fetch('/users', {
  headers: {
    Authorization: '********',
    lang: 'en'
  }
})

if(!res.ok) throw new Error(res.statusText)

users = await res.json()
```

## Motivation
So why go through this process? Well, first of all, it will make your code **easier to read**. You will get all the code from fetch hidden behind explanatory method calls. Like `backend.get.users()`, that makes sense and is short.

Sure you could extract the logic into a function named `getUsers()` and make the fetch call in there. But then here is the next benefit: **don't repeat your self**. Without a good structure for your back end requests, you will *definitely* repeat your self. Setting authorization and other headers in multiple places, or just to see if the fetch call is `ok` everywhere...

You could also move this code outside into a library for use in your web apps and Node services without a hassle. 

## Lets get started
[The code can be found here](https://codesandbox.io/s/organize-api-calls-js-0ox9v?file=/src/utils/http-client.js). 

So we will start of by making our own "mini, mini, mini version" of Axios (or insert name of http client here):

```javascript
class HttpClient {
  constructor(options = {}) {
    this._baseURL = options.baseURL || "";
    this._headers = options.headers || {};
  }
}
```
We start things off with the constructor, we will accept two options when instantiating the class:

**Base URL** will be used to construct the URL. Later we will use a get method like this `get('/users')` and if we defined the `baseURL` with `https://jsonplaceholder.typicode.com` the request URL will be `https://jsonplaceholder.typicode.com/users`

**Headers** will be a default set of headers that will be sent with every request. 

>If you think, "what is with the **_** before property names?" Well, I intend these properties to be *private*. Meant only to be used inside this (or inherited) classes. Not from anywhere outside in the actual app code. Soon there will be a syntax for this as well that implements the behavior, [check this out](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Private_class_fields)

We should probably add some way of setting headers as well: 
```javascript
setHeader(key, value) {
    this._headers[key] = value;
    return this;
  }
``` 
At the end of the setHeader method I added `return this`. This is added so we can `chain` method calls. For example when instantiating the HttpClient class:
```javascript
const httpClient = new HttpClient({baseURL: 'xxx'})
                     .setBasicAuth("user", "pass")
                     .setHeader("lang", "en")

```
Now in the example above I used another method `setBasicAuth`. I will skip that for now, but at the end of this post, you will find some inspiration for more properties and things you can add to the client. 

## Lets make requests!
This will be done in two steps. First of all we will define our own wrapper function for fetch, and then we will make separate methods for `get/post/put/delete/patch`: ```javascript
async _fetchJSON(endpoint, options = {}) {
  const res = await fetch(this._baseURL + endpoint, {
    ...options,
    headers: this._headers
  });

  if (!res.ok) throw new Error(res.statusText);

  if (options.parseResponse !== false && res.status !== 204)
    return res.json();

  return undefined;
}
```
So this wrapper function just makes fetch behave a little more like I want it to do in this specific use case. Like the fact that fetch does not throw on bad requests.   

The first argument is just the endpoint (string), and if we set the `baseURL` option it will be relative to that. 

And the options argument is just the fetch `Request` object we can add additional properties to. It can be empty, but more information about available [properties is found here](https://developer.mozilla.org/en-US/docs/Web/API/Request).

Oh! But I do append the `parseResponse` options to this argument to tell whether the response should be parsed to JSON or not. For most of my use cases, I want to opt-out of the parsing. So if left blank the parsing is done if not the API explicitly states `No Content`.

You could argue that we could check the content length or some other stuff, but what is good is that if I do require a response and I say I wanted it parsed. This method will throw if I do not get the response. So it will blow up here instead of in my application later where it might take me longer to find the cause.

Now lets expose some ways of making request. This should hopefully be straight forward:
```javascript
get(endpoint, options = {}) {
  return this._fetchJSON(
    endpoint, 
    { 
      ...options, 
      method: 'GET' 
    }
  )
}

post(endpoint, body, options = {}) {
  return this._fetchJSON(
    endpoint, 
    {
      ...options, 
      body: JSON.stringify(body), 
      method: 'POST' 
    }
  )
}

delete(endpoint, options = {}) {
  return this._fetchJSON(
    endpoint, 
    {
      parseResponse: false,
      ...options, 
      method: 'DELETE' 
    }
  )
}

/** AND SO ON */
```
We simply call our `_fetchJSON` method and set some options to make the HTTP method match our method name, and maybe set a body correct so that is taken care of.

Now we could just do some API calls:
```javascript
const httpClient = new HttpClient({baseURL: 'https://example.com'})
                     .setHeader('lang', 'sv')

const users = await httpClient.get('/users')
```
## One step further: The API client
We have done a lot! This client is our own "mini, mini, mini-version" of Axios. That we easily can extend with whatever parameters, options, or functions we would need.

But I want to take it one step further, I want to define our back end API with easy to call methods. Like I mentioned in the beginning. Now we can take one of two approaches. We could just add more methods to the HttpClient directly, and keep on working.

**But**, this class does serve its purpose now, right? It can work on its own, and be useful that way. So what if we let the HttpClient class be our base class and we can inherit that one to create our ApiClient class. 

This way we can make other HttpClients for talking to other services by using the HttpClient class directly. And talking to our backend with the ApiClient class, which just adds to the existing functionality.

Inheriting the HttpClient class would look like this:
```javascript
import HttpClient from "./http-client"

class ApiClient extends HttpClient { 
  constructor(baseURL, langCode) {
    super({
      baseURL,
      headers: {
        lang: langCode
      }
    });
  }

  get users() {
    return {
      get: () => this.get("/users"),
      delete: (id) => this.delete(`/users/${id}`),
      create: (user) => this.post("/users", user),
      update: (user) => this.put(`/users/${user.id}`, user)
    };
  }
}

export default ApiClient
```
Well, that was quite fast. We just added our little flavor to the constructor and we could simple and quickly define our endpoints. 

And adding additional endpoints is now really simple and reliable.

## Take it further
Now, this was a quick way to add basic functionality and then extending this to make the specific client. 

The idea here is to make the base as simple as possible and then add every feature you need instead of bringing in the full capacity of an external library upfront. 

Some things you could do next, if applicable to your needs of course:
#### Add helpers to authenticate if you do not rely on cookies
For example, if you need basic authentication:
```javascript
setBasicAuth(username, password) {
  this._headers.Authorization = `Basic ${btoa(`${username}:${password}`)}`
  return this
}
``` 
Just remember that btoa is not available globally in NodeJS. But just polyfill it and you will be good to go.

And for a bearer auth:
```javascript
setBearerAuth(token) {
  this._headers.Authorization = `Bearer ${token}`
  return this
}
``` 

#### Make the functions generic if you are on typescript
I do like typescript, 90% of all my code is in typescript. When building this for typescript add a generic return type to the functions, and for your post methods you should type the expected body:

```typescript
_fetchJSON<T = any>(endpoint: string, options: RequestInit = {}):Promise<T>{ /**/ }
``` 
I usually do a little hack when the body is not parsed and say `return undefined as any` to just get Typescript to not complain. If you expect undefined `T` should be `undefined` and you are good to go. 

And in your API client for a post request like this:
```typescript
users = {
  post: (user:IUser) => this.post<IUser>('/users', user)
}
```
#### Add methods to any applicable header your API expects/can use
For example at work we have a header to include or exclude empty values in the response (to save some time transferring large collections)
```javascript
includeEmptyAndDefault(shouldInclude) {
  if(shouldInclude) {
   this._headers.IncludeEmptyAndDefault = 1
  } else {
   this._headers.IncludeEmptyAndDefault = 0
  }
  return this
}
```
#### Build a package
Use [Rollup](https://rollupjs.org/guide/en/) or [tsdx, if you are into typescript,](https://github.com/formium/tsdx) if you want to do a separate package. This way the API client can be used as a module in other projects as well. That can be great for you, and your customers, to make things happen fast. 

But as I said, only add what you need. If you have any thoughts please share them in the comments and recommend me any patterns you like if this was not your cup of tea. 

> Cover photo by Florian Krumm on [Unsplash](https://unsplash.com/photos/1osIUArK5oA)