DRY Up your API Requests

Here’s how to avoid writing custom API requests 8 trillion times.

If you’re building a significant web app, you’re likely going to be interacting with a RESTful API on some level. Some people are happy importing axios in each of their React components and making a specific call on componentWillMount. This leads to repetitive, custom, and hard-to-debug code.

Let’s avoid this problem by creating axios utility functions that place requests for us, in a single, simple, standard way.

Proof is in the Pudding

When I start any project, one of the first things I do is define axios utilities and standard error handler.

// handleError.js - Common Error Handler Function
export default (error: any) {
const { status, message } = error;
switch (status) {
case 401:

// do something when you're unauthenticated
    case 403:
      // do something when you're unauthorized to access a resource
    case 500:
      // do something when your server exploded
    default:
      // handle normal errors with some alert or whatever
}
return message; // I like to get my error message back
}

Above, we wrote a simple function that performs custom actions depending on the error status code. I also like to return the error message so I can use that later to store in Redux, an alert, or whatever you please.

Now let’s write our API utilities file. This will give us a unified way to make GET, PATCH, POST, and DELETE requests, and handle any potential errors. Each function will return a promise so you can chain off them in your custom components.

// apiUtils.js
import axios from 'axios';
import handleError from './handleError';
const SERVER_DOMAIN = process.env.SERVER_DOMAIN;
const getHeaders = () {
return {
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
};
};
// HTTP GET Request - Returns Resolved or Rejected Promise
export const get = (path: string) => {
return new Promise((resolve, reject) => {
axios.get(`${SERVER_DOMAIN}${path}`, getHeaders())
.then(response => { resolve(response) })
.catch(error => { reject(handleError(error)) });
});
};
// HTTP PATCH Request - Returns Resolved or Rejected Promise
export const patch = (path: string, data: any) => {
return new Promise((resolve, reject) => {
axios.patch(`${SERVER_DOMAIN}${path}`, data, getHeaders())
.then(response => { resolve(response) })
.catch(error => { reject(handleError(error)) });
});
};
// HTTP POST Request - Returns Resolved or Rejected Promise
export const post = (path: string, data: any) => {
return new Promise((resolve, reject) => {
axios.post(`${SERVER_DOMAIN}${path}`, data, getHeaders())
.then(response => { resolve(response) })
.catch(error => { reject(handleError(error)) });
});
};
// HTTP DELETE Request - Returns Resolved or Rejected Promise
export const del = (path: string) => {
return new Promise((resolve, reject) => {
axios.delete(`${SERVER_DOMAIN}${path}`, getHeaders())
.then(response => { resolve(response) })
.catch(error => { reject(handleError(error)) });
});
};

Amazing! Now anytime we want to make a HTTP GET request, you don’t have to fiddle with importing axios, creating your headers, and managing request domains. Simple import “get” from “apiUtils.js” and you’re good to go.

Here’s an example:

import { get } from './apiUtils';
get('/user/5')
.then(data => {

// do something with User #5
  })
.catch(errorMessage => {
    // the error has already been handled by handleError
// the message get's passed here
// do something like store it in redux, etc.
  });

Look at that DRY DRY code! Your API request logic and generic error handling is isolated to one file. That means clean, testable, maintainable, and “easy to reason about” code for you and your team. Anytime you need to modify the logic in any of your requests, headers, or error handling code, you just have to write it once, and you’re good to go.

It’s the little things :)