Axios HTTP Client Using TypeScript
Altrim Beqiri /
Whenever I plan to use axios on my projects I tend to create a tiny wrapper around it. By doing so I can expose only a subset of the methods and use only the parts I need from axios. Moreover I feel I can easily change the implementation details in the future to use fetch or any other library underneath without affecting its' usage.
Additionally as we can see from the following code we have also specified default headers that we attach to the requests, an interceptor to inject the JWT
token in headers and another one to handle few generic errors.
// http.ts
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios";
enum StatusCode {
Unauthorized = 401,
Forbidden = 403,
TooManyRequests = 429,
InternalServerError = 500,
}
const headers: Readonly<Record<string, string | boolean>> = {
Accept: "application/json",
"Content-Type": "application/json; charset=utf-8",
"Access-Control-Allow-Credentials": true,
"X-Requested-With": "XMLHttpRequest",
};
// We can use the following function to inject the JWT token through an interceptor
// We get the `accessToken` from the localStorage that we set when we authenticate
const injectToken = (config: AxiosRequestConfig): AxiosRequestConfig => {
try {
const token = localStorage.getItem("accessToken");
if (token != null) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
} catch (error) {
throw new Error(error);
}
};
class Http {
private instance: AxiosInstance | null = null;
private get http(): AxiosInstance {
return this.instance != null ? this.instance : this.initHttp();
}
initHttp() {
const http = axios.create({
baseURL: "https://api.example.com",
headers,
withCredentials: true,
});
http.interceptors.request.use(injectToken, (error) => Promise.reject(error));
http.interceptors.response.use(
(response) => response,
(error) => {
const { response } = error;
return this.handleError(response);
}
);
this.instance = http;
return http;
}
request<T = any, R = AxiosResponse<T>>(config: AxiosRequestConfig): Promise<R> {
return this.http.request(config);
}
get<T = any, R = AxiosResponse<T>>(url: string, config?: AxiosRequestConfig): Promise<R> {
return this.http.get<T, R>(url, config);
}
post<T = any, R = AxiosResponse<T>>(
url: string,
data?: T,
config?: AxiosRequestConfig
): Promise<R> {
return this.http.post<T, R>(url, data, config);
}
put<T = any, R = AxiosResponse<T>>(
url: string,
data?: T,
config?: AxiosRequestConfig
): Promise<R> {
return this.http.put<T, R>(url, data, config);
}
delete<T = any, R = AxiosResponse<T>>(url: string, config?: AxiosRequestConfig): Promise<R> {
return this.http.delete<T, R>(url, config);
}
// Handle global app errors
// We can handle generic app errors depending on the status code
private handleError(error) {
const { status } = error;
switch (status) {
case StatusCode.InternalServerError: {
// Handle InternalServerError
break;
}
case StatusCode.Forbidden: {
// Handle Forbidden
break;
}
case StatusCode.Unauthorized: {
// Handle Unauthorized
break;
}
case StatusCode.TooManyRequests: {
// Handle TooManyRequests
break;
}
}
return Promise.reject(error);
}
}
export const http = new Http();
Now that we have our http
client ready we can use it in a service and we will get proper autocompletion and type hinting as we code.
// users.service.ts
import { http } from "./http";
export type User = {
id: string;
name: string;
avatar: string;
email: string;
};
export const fetchUsers = async (): Promise<User[]> => {
return await http.get<User[]>("/users");
};
export const createUser = async (user: User): Promise<User> => {
return await http.post<User>("/users", user);
};
export const updateUser = async (user: User): Promise<User> => {
return await http.put<User>(`/users/${user.id}`, user);
};
export const deleteUser = async (user: User): Promise<User> => {
return await http.delete<User>(`/users/${user.id}`);
};
Finally, say if we are using React we can use the service in a component and again we will get proper type hinting, type checking and autocompletion.
// UsersPage.tsx
import React, { useState, useEffect } from "react";
import { fetchUsers, User } from "./users.service";
const UsersPage: React.FC = () => {
const [users, setUsers] = useState<User[]>([]);
useEffect(() => {
const fetchData = async () => {
// Here we get users: User[]
const users = await fetchUsers();
setUsers(users);
};
fetchData();
}, []);
return (
<div>
{users?.map((user) => (
<div key={user.id}>
<img src={user.avatar} alt={user.name} />
<div>{user.name}</div>
<div>{user.email}</div>
</div>
))}
</div>
);
};