Custom Error Types in Axios TypeScript Setup
Handling API errors with proper type safety is essential for robust TypeScript applications. This guide demonstrates the syntax and patterns required to define and handle custom error types when using Axios in a TypeScript setup, enabling you to capture strongly typed backend error payloads, status codes, and application-specific exceptions.
1. Define the API Error Response Payload
First, define a TypeScript interface or
type that represents the JSON structure returned by your
backend when an error occurs.
export interface ApiErrorPayload {
status: number;
message: string;
code?: string;
errors?: Record<string, string[]>;
}2. Using AxiosError with Generics
Axios provides the generic AxiosError<T> type,
where T corresponds to the response body type. You can
type-alias this directly:
import { AxiosError } from 'axios';
export type CustomAxiosError = AxiosError<ApiErrorPayload>;3. Creating a Custom Error Class
To encapsulate error data into a standardized domain object, extend
JavaScript's built-in Error class:
export class ApiError extends Error {
public readonly statusCode: number;
public readonly errorCode?: string;
public readonly validationErrors?: Record<string, string[]>;
constructor(message: string, statusCode: number, errorCode?: string, validationErrors?: Record<string, string[]>) {
super(message);
this.name = 'ApiError';
this.statusCode = statusCode;
this.errorCode = errorCode;
this.validationErrors = validationErrors;
// Restore prototype chain for instanceof checks
Object.setPrototypeOf(this, ApiError.prototype);
}
}4. Transforming Errors with an Axios Interceptor
Use Axios interceptors to catch raw network errors, extract typed backend payloads, and throw your custom error class.
import axios, { AxiosInstance, AxiosError } from 'axios';
const apiClient: AxiosInstance = axios.create({
baseURL: 'https://api.example.com',
});
apiClient.interceptors.response.use(
(response) => response,
(error: AxiosError<ApiErrorPayload>) => {
if (error.response) {
// Backend returned an error response (4xx, 5xx)
const { data, status } = error.response;
throw new ApiError(
data.message || 'An error occurred',
status,
data.code,
data.errors
);
} else if (error.request) {
// Request was made but no response was received (Network error)
throw new ApiError('Network error: No response from server', 0);
} else {
// Request configuration issue
throw new ApiError(error.message, -1);
}
}
);
export default apiClient;5. Type Narrowing with Type Guards
When catching errors in try/catch blocks, TypeScript
types caught variables as unknown. Use type guards to
narrow them to your custom types:
import axios from 'axios';
async function fetchUserData(userId: string) {
try {
const response = await apiClient.get(`/users/${userId}`);
return response.data;
} catch (error: unknown) {
if (error instanceof ApiError) {
console.error(`API Error [${error.statusCode}]:`, error.message);
if (error.validationErrors) {
console.error('Validation details:', error.validationErrors);
}
} else if (axios.isAxiosError<ApiErrorPayload>(error)) {
console.error('Direct Axios Error:', error.response?.data.message);
} else {
console.error('Unexpected Error:', error);
}
}
}