Skip to content

Miedziak.io

Github

Axios service architecture

js, axios, architecture, api2 min read

Introduction

Thanks to axios we gain lots of power when it comes to executing and consuming REST API, but it don't gives us any "ready to use" pattern how to use and structure them in correct way.

I want to share with you my approach about how do I organize and implement services while using axios library.

In this post, I'm not going to cover basics about using axios services, and I assume you have got basic knowledge about axios and ES6 at least.

The goal will be to create API service files structure which would fulfil the criteria:

  • extendable, maintainable, readable.
  • separate endpoints path group for separate files/service ( ie. /posts /comments /users)
  • possibility to create and manage many root apis with different host url (many separate REST service), but still sharing functionality of each other (like interceptors).
  • adding interceptors for all services (i.e. global errors), particular one (endpoint specific errors), or even for particular endpoints
  • support for migrating to newest API version (i.e https://api.exmaple.com/v2/* )

Implementation

For this articule purpose I will use fake online REST API - https://jsonplaceholder.typicode.com

I. axiosProvider

In first step we create simple function that will return our axios instance, so that we could use it independently on each level of our structure.

src/api/axiosProvider.js
1import axios from 'axios';
2
3const defaultOptions = {};
4
5function axiosProvider(baseUrl, options) {
6 console.log('creating axios instance')
7 return axios.create({
8 baseURL: baseUrl,
9 ...defaultOptions,
10 ...options
11 });
12}
13
14export default axiosProvider;

defaultOptions variable is empty for now, but keep in mind it's nice place to set our defaults options for axios.

II. CoreApi class

Next step is to create our basic class that we will be using across all of our services.

src/api/CoreApi.js
1import axiosProvider from './axiosProvider';
2
3class CoreApi {
4 constructor(baseUrl, slug = '') {
5 this.baseUrl = baseUrl;
6 this.slug = slug;
7 this.api = axiosProvider(`${this.baseUrl}${this.slug}`);
8 this.setInterceptors({
9 beforeRequest: this._beforeRequest,
10 requestError: this._requestError,
11 afterResponse: this._afterResponse,
12 responseError: this._responseError,
13 });
14 };
15
16 setInterceptors = ({
17 beforeRequest,
18 requestError,
19 afterResponse,
20 responseError,
21 }) => {
22 this.api.interceptors.request.use(beforeRequest, requestError);
23 this.api.interceptors.response.use(afterResponse, responseError);
24 };
25 ...

In a constructor, we pass two parameters:

  • baseUrl - later on, we would pass here service main url like i.e - 'https://jsonplaceholder.typicode.com'
  • slug - will be like cluster under which we will group our requests -> ie. '/posts'. So for /posts would could have many requests like GET /posts POST /posts but also GET /posts/search?userId=1

We are using both this parameters, to create axios instance with default baseUrl, so that later on we could use it just like this.api.get('') or this.api.get('/search?userId=' + id)

In line number 8, we are also setting interceptors that are defined underneath our constructor. It's implementation details is not crucial for now, se we skipp this.

But what important to mention, is that this interceptors will be global. What that mean is any services that we would create later on, would be using/invoking this interceptors in request cycle.

III. apiProvider

We are almost there. There is one more class that we should create to keep it flexible and extensible. This class would help us to use many APIs in our project, but still sharing some functionality between them ( ie. handling errors, share tokens, etc.)

It's basic implementation could looks something like this:

src/api/serviceProviders/PlaceholderApiProvider.js
1import CoreApi from '../CoreApi';
2import config from '../../config';
3
4class PlaceholderApiProvider extends CoreApi {
5 constructor(endpoint) {
6 super(config.placeholderApiUrl, endpoint);
7 }
8}
9
10export default placeholderApiProvider;

In our application we could have many independent API, each on different url, that we have to working on ( ie. authorization, weather, currency, cms ) . The goal here, is to create class that would be used later on to create each of our services related to particular API.

So here we just invoking CoreApi constructor with appropriate apiUrl from config -> config.placeholderApiUrl. In our case this value is https://jsonplaceholder.typicode.com

Keep in mind, that we could set interceptors for this particular API, just by invoking this.setInterceptors({ ... }) in the constructor. It would invoke independently from our global interceptors from CoreApi file.

IV. Finally... Service !!

Ok, now we are ready to go with service itself. So we create class based on placeholderApiProvider. It will be responsible to manage bunch of endpoints related to particular resources ( ie. /posts ).

src/api/services/PostsService.js
1import PlaceholderApiProvider from '../serviceProviders/placeholderApiProvider';
2
3class PostsService extends PlaceholderApiProvider {
4 async getAll() {
5 return this.api.get();
6 }
7
8 async getById(id) {
9 return this.api.get(`/${id}`);
10 }
11
12 async getCommentsForPost(id) {
13 return this.api.get(`/${id}/comments`);
14 }
15
16}
17
18const postsService = new PostsService('/posts');
19
20export default postsService;
21export { PostsService };

So this is our goal service. We implement methods like getAll getById getCommentsForPost that we could use later on in our Sagas or Components.

V. Working example

You can find working example based on react and create-react-app:

Github

https://github.com/miedziak/axios-services-architecture-example

codesandbox.io

Edit axios-service-architecture-sample