Typing Axios Error Responses in TypeScript

Handling and typing API error responses correctly in TypeScript with Axios requires moving away from unsafe type assertions (as AxiosError) and leveraging Axios's built-in type guards. Since TypeScript sets errors in catch blocks to unknown by default, the proper approach involves defining a custom interface for your backend error payload and validating the error using axios.isAxiosError<T>(). This guide explains how to strongly type Axios error responses safely and efficiently.

1. Define the Expected Error Response Interface

Backends typically return a structured JSON response when a request fails (e.g., a 400 or 500 status code). Define a TypeScript interface that mirrors this schema:

interface ApiErrorResponse {
  message: string;
  statusCode: number;
  errors?: Record<string, string[]>;
}

2. Use axios.isAxiosError as a Type Guard

Axios provides a built-in type guard called axios.isAxiosError. This function checks if an unknown error object is an AxiosError at runtime and narrows the type in TypeScript.

You can pass your error response interface as a generic type argument to axios.isAxiosError<T>:

import axios from 'axios';

async function fetchUserData(userId: string) {
  try {
    const response = await axios.get(`/api/users/${userId}`);
    return response.data;
  } catch (error: unknown) {
    if (axios.isAxiosError<ApiErrorResponse>(error)) {
      // error is now typed as AxiosError<ApiErrorResponse>
      if (error.response) {
        // The server responded with a status code outside the 2xx range
        console.error('API Error Message:', error.response.data.message);
        console.error('Status Code:', error.response.status);
      } else if (error.request) {
        // The request was made but no response was received (e.g., network error)
        console.error('Network Error: No response received');
      } else {
        // Something went wrong setting up the request
        console.error('Axios Setup Error:', error.message);
      }
    } else {
      // Non-Axios error (e.g., native JavaScript runtime error)
      console.error('Unexpected Error:', error);
    }
  }
}

3. Handle Axios Response Typing in Custom Utilities

If you are writing a reusable wrapper or service layer, you can create a helper function to extract error messages safely:

import axios from 'axios';

export function getErrorMessage(error: unknown): string {
  if (axios.isAxiosError<ApiErrorResponse>(error)) {
    return error.response?.data?.message ?? error.message;
  }
  if (error instanceof Error) {
    return error.message;
  }
  return 'An unknown error occurred';
}

4. Why Avoid Direct Type Casting (error as AxiosError)

Direct casting with catch (error) { const err = error as AxiosError; } bypasses TypeScript’s runtime safety. If an unhandled JavaScript exception occurs inside your try block (such as a TypeError from parsing JSON), treating it unconditionally as an AxiosError can cause error.response lookups to fail or produce runtime bugs. Using axios.isAxiosError() ensures your code only accesses Axios-specific properties when the error actually originates from an Axios request.