In my recent project, we have written multiple express APIs for different purposes and called them from react code. In this case multiple APIs have their different routes, definition and responses. Every api is having their CRUD operations and we have written separate code to call every api.
What this will lead to ? Code duplication and Code mess.
So I was thinking what I can do to avoid this mess and have a simple approach to call these APIs.
I spent time on analyzing the code we have written to call these APIs, what are the code duplication sections? can we make this generic any how?
As a result of analysis, I found that for every api call we have set of functions which can be minimized to generic ones and call for every API.
Following are set of things I have implemented for refactoring of API calls-
1. Division of code
In each place of API call, I found that we have performed CRUD (Create, Read, Update, Delete) operation only which can be moved to separate files and only difference is the name of resource e.g. /v1/api/users
/v1/api/companies
So users, companies are nothing but our resource the first part of api is same for all.
Keeping all these things in mind, we made following division-
api.provider.ts :
This file is having CRUD operation definition for API calls. It includes axios calling as its promise based and we can handle the responses the way we wanted.
// Define your api url from any source. Pulling from your .env // file when on the server or from localhost when locally
const BASE_URL = Api_base_CRUD;
/** @param {string} resource */
const getAll = (resource: string) => {
return axios
(`${BASE_URL}/${resource}`)
.then(resp => resp.data)
.catch(handleError);
};
/** @param {string} resource */
/** @param {string} id */
const getSingle = (resource: string, id: string) => {
return axios
.get(`${BASE_URL}/${resource}/${id}`)
.then(resp => resp.data)
.catch(handleError);
};
/** @param {string} resource */
/** @param {object} model */
const post = (resource: string, model: object) => {
return axios
.post(`${BASE_URL}/${resource}`, model)
.then(resp => resp.data)
.catch(handleError);
};
/** @param {string} resource */
/** @param {object} model */
const patch = (resource: string, model: object) => {
return axios
.patch(`${BASE_URL}/${resource}`, model)
.then(resp => resp.data)
.catch(handleError);
};
/** @param {string} resource */
/** @param {string} id */
const remove = (resource: string, id: AxiosRequestConfig<any> | undefined) => {
return axios
.delete(`${BASE_URL}/${resource}/${id}`, id)
.then(resp => resp.data)
.catch(handleError);
};
api.core.ts :
This is a class from where we can make calls to the provider file methods. Here we can pass the resource urls as well.
import {apiProvider} from './api-provider';
export class ApiCore {
getAll!: () => any;
getSingle!: (id: any) => any;
post!: (model: any) => any;
patch!: (model: any) => any;
remove!: (id: any) => any;
url!: string;
constructor(options: { getAll: any; url: any; getSingle: any; post: any; patch: any; remove: any}) {
if (options.getAll) {
this.getAll = () => {
return apiProvider.getAll(this.url);
};
}
if (options.getSingle) {
this.getSingle = (id) => {
return apiProvider.getSingle(this.url, id);
};
}
if (options.post) {
this.post = (model) => {
return apiProvider.post(this.url, model);
};
}
if (options.patch) {
this.patch = (model) => {
return apiProvider.patch(options.url, model);
};
}
if (options.remove) {
this.remove = (id) => {
return apiProvider.remove(this.url, id);
};
}
}
}
api.operation.ts :
This will be the actual file we will be making use of when making api calls, this includes making an object of api-core class and specify the parameters for the constructor.
import { ApiCore } from "./api-core";
const apiOperation = new ApiCore({
getAll: true,
getSingle: true,
post: true,
patch: true,
remove: true,
url: "",
});
export default apiOperation;
2. Implementing API calls
Now its time to make calls to our api using the generic api files we have created.
import apiUsers from '../../api-operation';
function callUsersData(){
apiUsers.url = "users";
apiUsers.getAll()
.then((resp:any) => {
let user = resp.data?.rows;
})
}
The only thing that will be different in each api is their url, everything else is generic now.
Conclusion :
By making division of files and using the generic functions for api call, now the code base looks simple, easy to read and mainly we removed code duplication.
I hope this helps you manage your API code structure easily manageable and understandable as your code base and team grows!
Here is a link of reference used while doing implementation :
https://dev.to/mmcshinsky/a-simple-approach-to-managing-api-calls-1lo6
Happy Reading :)
Top comments (2)
Great article! It is very well written
Thanks for sharing!