Axios HTTP Client Using TypeScript
Altrim Beqiri /
Whenever I plan to use axios on my projects I typically develop a small wrapper around it. This approach allows me to expose only a subset of the methods and utilize 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 illustrated in the following code snippet we have also specified default headers that we can append 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 injectToken function to inject the JWT token through an interceptor
// We get the `accessToken` from the `localStorage` which is stored during the authentication process
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();
With our http
client set up, we can integrate it into a service, which will provide enhanced autocompletion and type hinting during coding.
// 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 incorporating the service into a component will ensure robust type hinting, type checking, and autocompletion features.
// 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>
);
};