Create an awesome JS API interface using Fetch (in less than 50 lines)
Tags: javascript, tutorial, webdev
Published: 2019-10-11 02:11:37

---
title: Create an awesome JS API interface using Fetch (in less than 50 lines)
published: true
description: Create an awesome JS API interface using Fetch
tags: javascript, tutorial, webdev
---

In this tutorial, we will create a **single reusable module that can perform all of our API calls** in less than 50 lines of code! [(Jump to final module code here)](#final-code). If you're familiar with the fetch API, you've seen how it could start to look ugly and unreadable with all of the promise chains.

```javascript
fetch(url)
  .then((response) => response.json())
  .then(data => {
    console.dir(data);
  });
})
```
**Ew.** You don't want to be doing that for every API call in your app. We should abstract this into a module so that your calls become more legible and easier to make. API calls will then look like this:

```javascript
async function getArticles(userId) {
  const articles = await Fetch.get('/articles/' + userId);
  articles.forEach(item => {
   // iterate through results here...
  });
}
```
We will be using ES6 modules in this tutorial. **Let's go!** (Take note of async and await keywords).

### Create a Fetch.js module skeleton
First, let's define our public CRUD methods that we will use to interact with our module and define the API host:
```javascript
// Fetch.js
const _apiHost = 'https://api.service.com/v1';

export default {
  get,
  create,
  update,
  remove
};
```

### Create the main fetch method
Next, let's make a private method inside our module that performs the actual fetching. This general purpose method should be able to handle both read and write API calls. Our method will take 3 arguments:

1. **url** - the API endpoint - ex: '/articles'
2. **params** - additional parameters to pass to the endpoint as either a query string for GET or data for the body of the request on POST, UPDATE, and DELETE calls
3. **method** - GET (default), POST, PUT, DELETE
```javascript
// Fetch.js
// ...

async function request(url, params, method = 'GET') {

}

// ...
```

Notice the **async** at the beginning of our function. We need this because we are going to deal with [Promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise).

### Add options and make sure to use "await"
```javascript
// Fetch.js
const _apiHost = 'https://api.service.com/v1';

async function request(url, params, method = 'GET') {

  // options passed to the fetch request
  const options = {
    method
  };

  // fetch returns a promise, so we add keyword await to wait until the promise settles
  const response = await fetch(_apiHost + url, options);
  const result = await response.json(); // convert response into JSON

  // returns a single Promise object
  return result;

}

// ...
```

### Handle parameters for all request types
We want this method to be able to handle any parameters that may need to be included as part of the API request. For **GET** requests, we need to support query string . For all other write-related requests, we need to be able to include a body to the request.

We want our module to be super easy to use so let's standardize on handling **objects** for all cases. We can then convert the object into a query string for **GET** requests and include the object as JSON as part of the request body for all other types.

Let's create our object to query string conversion method:
```javascript
// Fetch.js

// ...

// converts an object into a query string
// ex: {authorId : 'abc123'} -> &authorId=abc123
function objectToQueryString(obj) {
  return Object.keys(obj).map(key => key + '=' + obj[key]).join('&');
}

// ...
```

Now we can add some code to handle the parameters if they exist. Let's add this right after the options definition in our request method:
```javascript
// Fetch.js
const _apiHost = 'https://api.service.com/v1';

async function request(url, params, method = 'GET') {

  const options = {
    method,
    headers: {
      'Content-Type': 'application/json' // we will be sending JSON
    }
  };

  // if params exists and method is GET, add query string to url
  // otherwise, just add params as a "body" property to the options object
  if (params) {
    if (method === 'GET') {
      url += '?' + objectToQueryString(params);
    } else {
      options.body = JSON.stringify(params); // body should match Content-Type in headers option
    }
  }

  const response = await fetch(_apiHost + url, options);
  const result = await response.json();

  return result;

}

function objectToQueryString(obj) {
  return Object.keys(obj).map(key => key + '=' + obj[key]).join('&');
}

// ...
```

### Create public methods
**Excellent!** Now let's create the four public methods that we can use to make our different requests. We will export a single object that references these four methods at the very bottom of our module.
```javascript
// Fetch.js
// ...
function get(url, params) {
  return request(url, params);
}

function create(url, params) {
  return request(url, params, 'POST');
}

 function update(url, params) {
  return request(url, params, 'PUT');
}

function remove(url, params) {
  return request(url, params, 'DELETE');
}
```

### Error Handling
It's a good idea to add some error handling to our module. One way is to check the status code of the response. If it is NOT 200, then we should return some kind of error message.

You may handle errors any way you want. In this example, we will return an object with a status and message. Let's check the status immediately after our fetch request:
```javascript
// Fetch.js

// ...

  const response = await fetch(_apiHost + url, options);

  // show an error if the status code is not 200
  if (response.status !== 200) {
    return generateErrorResponse('The server responded with an unexpected status.');
  }

  const result = await response.json();

  return result;

}

// A generic error handler that just returns an object with status=error and message
function generateErrorResponse(message) {
  return {
    status : 'error',
    message
  };
}
```

### We did it!
Now we can make all four types of API requests using a single module!

#### async / await
Our Fetch calls return a single promise so we need to use the await keyword in front of our calls. Additionally, await keyword does not work on top level code so they must be wrapped inside an async function. Here's what these requests may look like:
```javascript
import Fetch from './Fetch.js';

// GET
async function getAllBooks() {
  const books = await Fetch.get('/books');
}

// POST
async function createBook() {
  const request = await Fetch.create('/books', {
    title: 'Code and Other Laws of Cyberspace',
    author: 'Lawrence Lessig'
  });
}

// PUT
async function updateBook(bookId) {
  const request = await Fetch.update('/books/' + bookId, {
    title: 'How to Live on Mars',
    author: 'Elon Musk'
  });
}

// DELETE
async function removeBook(bookId) {
  const request = await Fetch.remove('/books/' + bookId);
}
```

### Our completed API interface module! - Next Steps <a name="final-code"></a>
I hope that you enjoyed this tutorial and that you can use it to become more productive!

You can add all sorts of other options to this module and in the fetch request itself. **For example, what parameters would you add to the fetch method to support CORS requests?**

How would you handle **file uploads**?

#### Happy Fetching!

```javascript
// Fetch.js
const _apiHost = 'https://api.service.com/v1';

async function request(url, params, method = 'GET') {

  const options = {
    method,
    headers: {
      'Content-Type': 'application/json'
    }
  };

  if (params) {
    if (method === 'GET') {
      url += '?' + objectToQueryString(params);
    } else {
      options.body = JSON.stringify(params);
    }
  }

  const response = await fetch(_apiHost + url, options);

  if (response.status !== 200) {
    return generateErrorResponse('The server responded with an unexpected status.');
  }

  const result = await response.json();

  return result;

}

function objectToQueryString(obj) {
  return Object.keys(obj).map(key => key + '=' + obj[key]).join('&');
}

function generateErrorResponse(message) {
  return {
    status : 'error',
    message
  };
}

function get(url, params) {
  return request(url, params);
}

function create(url, params) {
  return request(url, params, 'POST');
}

 function update(url, params) {
  return request(url, params, 'PUT');
}

function remove(url, params) {
  return request(url, params, 'DELETE');
}

export default {
  get,
  create,
  update,
  remove
};

```