How to Type Axios Response Data in TypeScript

Axios provides built-in support for TypeScript generics, allowing developers to define predictable data contracts for HTTP requests. By typing your API responses, you gain compile-time type safety, IDE auto-completion, and fewer runtime bugs. This guide demonstrates how to pass generic types to Axios methods, manage the AxiosResponse interface, create reusable API wrappers, and type custom error responses.

Basic Typing with Axios Generics

Axios request methods such as get, post, put, and delete accept generic type arguments. When you pass a type to these methods, TypeScript assigns that type to the data property of the returned AxiosResponse object.

import axios from 'axios';

interface User {
  id: number;
  name: string;
  email: string;
}

async function fetchUser(userId: number): Promise<User> {
  // Pass the interface as a generic argument to axios.get
  const response = await axios.get<User>(`https://api.example.com/users/${userId}`);
  
  // response.data is typed as User
  return response.data;
}

Understanding the AxiosResponse Wrapper

By default, Axios methods return a promise containing an AxiosResponse<T> object. The generic parameter T defines the shape of response.data.

The AxiosResponse interface is structured roughly as follows:

interface AxiosResponse<T = any> {
  data: T;
  status: number;
  statusText: string;
  headers: Record<string, string>;
  config: AxiosRequestConfig;
  request?: any;
}

When you write axios.get<User>(), TypeScript resolves the return value to Promise<AxiosResponse<User>>.

Typing POST, PUT, and PATCH Requests

For methods that send a request body, Axios allows you to type both the response and the request payload:

interface CreateUserDto {
  name: string;
  email: string;
}

interface CreateUserResponse {
  id: number;
  createdAt: string;
}

async function createUser(payload: CreateUserDto): Promise<CreateUserResponse> {
  // axios.post<ResponseType, AxiosResponse<ResponseType>, RequestPayloadType>
  const response = await axios.post<CreateUserResponse>(
    'https://api.example.com/users',
    payload
  );

  return response.data;
}

Creating a Strongly Typed API Client

To avoid repeatedly writing response.data and unwrapping AxiosResponse, you can create a reusable API client utility that returns the payload directly.

import axios, { AxiosRequestConfig } from 'axios';

const apiClient = axios.create({
  baseURL: 'https://api.example.com',
  headers: {
    'Content-Type': 'application/json',
  },
});

export const api = {
  get: async <T>(url: string, config?: AxiosRequestConfig): Promise<T> => {
    const response = await apiClient.get<T>(url, config);
    return response.data;
  },
  post: async <T, B = unknown>(url: string, body?: B, config?: AxiosRequestConfig): Promise<T> => {
    const response = await apiClient.post<T>(url, body, config);
    return response.data;
  },
};

// Usage:
const user = await api.get<User>('/users/1'); // user is directly inferred as User

Typing Axios Errors

When requests fail, Axios throws an error that can be cast and checked using the isAxiosError helper. You can pass a generic type to AxiosError to define the structure of the API error response.

import axios, { AxiosError } from 'axios';

interface ApiErrorResponse {
  message: string;
  errorCode: number;
}

async function safeFetchUser(userId: number) {
  try {
    const response = await axios.get<User>(`https://api.example.com/users/${userId}`);
    return response.data;
  } catch (error) {
    if (axios.isAxiosError<ApiErrorResponse>(error)) {
      // error.response?.data is typed as ApiErrorResponse | undefined
      console.error(error.response?.data.message);
      console.error(`Error Code: ${error.response?.data.errorCode}`);
    } else {
      console.error('Unexpected error:', error);
    }
  }
}

Runtime Validation Considerations

TypeScript generics provide compile-time guarantees, but they do not validate the data at runtime. If an API returns an unexpected payload structure, TypeScript will still treat it as the specified generic type. For mission-critical applications, combine Axios generics with runtime schema validation libraries like Zod to ensure received data strictly matches your types before processing.