Architecting HTTP client in Vue js (Nuxt js)

In this article, I want to talk about the backend APIs and how to manage them in our app.
First of all, we have to choose the modules in such a way that we chose our models.
Let me explain:
Assume that you have two data models, User, and Book.
Each of the User and Book models has its own API, Like registering a user or buying a book.
So we have to encapsulate each API for a specific model.
Let’s get started
In our Nuxt app create a folder named API (or whatever you comfortable with)

1.Creating HTTP Clients using Axios instances
2.Structuring your API endpoints
3.Making network requests inside Vuex actions
4.Handling network errors

Axios provides out of the box support for having a persistent configuration for all of our API calls using Axios instances. We’ll be using Axios instances as HTTP clients in our application with our configurations. If you are working on a large-scale application, it is possible that your application needs to communicate with different API endpoints. In this case, we might need to create multiple Axios instances, with their own configuration and separate them out to individual files.

Install Axios in your project
$ npm install --save axios

Always consider best prictices. it is recommended to add API URLs into .env file
In .env file in the nuxt app we must add NUXT to variables. For example: NUXT_ENV_BASE_URL=https://api.myapp.test
read the documents

Once we have our environment variables in place, we can retrieve them while creating axios instances. We can additionally pass all our configuration into this instance, including headers, and use this instance to create HTTP requests.
let’s create our http client instance in httpclient.js placed in API folder.

import axios from 'axios';

const httpClient = axios.create({
  baseURL: process.env.baseUrl,
  headers: {
    "Content-Type": "application/json",
    // anything you want to add to the headers
  }
});

export default httpClient;

Structuring your API endpoints

As I mentioned, we need ENCAPSULATION. So you should create a folder named user in the API folder and add the users.api.js file inside it.
in users.api.js:

import httpClient from '../httpclient';


export const login = ({username, password}) => {
  return httpClient({
    url: '/login',
    method: 'post',
    headers: {
      // "X-CSRF-TOKEN": getCookie('csrf_token')
    },
    data: {
      username, password
    }
  });
}

export const register = ({username, password}) => {
  return httpClient({
    url: '/register',
    method: 'post',
    headers: {
      // "X-CSRF-TOKEN": getCookie('csrf_token')
    },
    data: {
      username, password
    }
  });
}

We can follow a simple directory structure for storing all these files.

api/
├── httpClient.js --> HTTP Client with our configs
├── users
├──── users.api.js
├── books
├──── books.api.js

And we can use them in our Vue.js components and Vuex store by simply importing them.

import { login, register } from '@/api/users/users.api';

Making network requests inside Vuex actions Move all the business login into Vuex store, including network requests makes the view components independent. Use actions to fetch data and store them in state using mutations. (Don’t change states in actions directly) actions are synchronous by default, but you can use Promises or async to check if the action is complete or not. (please try to split modules in Vuex store as well as the APIs).

Here is an example:

/*
*   store/modules/users/users.module.js
*/
import {register, login} from "~/api/users/users.api";

export const state = () => ({
  user: null,
  csrf_token: null
})

export const getters = {
  get_user(state) {
    return state.user
  },
}
export const mutations = {
  SET_USER_INFO(state, user) {
    state.user = user
  },
}
export const actions = {
  loginUser({store, commit}, {username, password}) {
  return new Promise(async (resolve, reject) => {
    try {
      const response = await login({
        username, password
      });
      //depends on the response
      commit('SET_USER_INFO', response.data) 
      resolve()
    } catch (err) {
      reject(err.response.data.message)
      console.log(err)
    }
  })
}
}

Handling network errors and logging

Is it easy as response.status === 500 in every request? It's not ideal to check the status and logging these errors in every network request we make inside our actions. Instead, axios offer abilities to intercept the error responses, which is a perfect spot to find errors, log or show a cute notification to the user saying the server effed up. We can also use this to log-out the user from your application if the requests aren't authorized or if the server informs of an expired session.

import axios from 'axios';

const httpClient = axios.create({
  headers: {
    "Content-Type": "application/json",
  }
})

// interceptor to catch errors
const errorInterceptor = error => {
  // check if it's a server error
  if (!error.response) {
    return Promise.reject(error);
  }

  // all the other error responses
  switch (error.response.status) {

    case 401: // authentication error, logout the user
      localStorage.removeItem('auth_token');
      stop();
      location.href = '/auth/login';
      break;

    default:
  }
  return Promise.reject(error);
}

// Interceptor for responses
const responseInterceptor = response => {
  switch (response.status) {
    case 200:
      // yay!
      break;
    // any other cases
    default:
    // default case
  }

  return response;
}

httpClient.interceptors.response.use(responseInterceptor, errorInterceptor);

export default httpClient;

This is an example of how you can manage errors. (there are several ways to do that).
That’s it. if you follow this architecture for implementing your client API, you can scale it very easy, Also your codes become maintainable and it’s a very important point, especially for big projects.

Thanks for reading this article 💖. Liked the article? have some feedback or suggestions? leave a like and a comment. This will help me understand better and write more amazing articles for you 🙂.

25