axios: How to type axios error in Typescript?

Describe the issue

I have question how type axios error in Typescript. I know AxiosError type is exposed, but I don’t think it should be used because it’s unclear if it’s an Axios Error when caught.

Example Code

import axios, {AxiosError} from 'axios';
axios.get('v1/users')
  .catch((e: AxiosError) => { // really AxiosError?
      console.log(e.message);
  }

Should I do something judgement before typing with AxiosError? Please tell me a good practice.

Expected behavior, if applicable

A clear and concise description of what you expected to happen.

Environment

  • Axios Version [e.g. 0.18.0]
  • Adapter [e.g. XHR/HTTP]
  • Browser [e.g. Chrome, Safari]
  • Browser Version [e.g. 22]
  • Node.js Version [e.g. 13.0.1]
  • OS: [e.g. iOS 12.1.0, OSX 10.13.4]
  • Additional Library Versions [e.g. React 16.7, React Native 0.58.0]

Additional context/Screenshots

Add any other context about the problem here. If applicable, add screenshots to help explain.

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Reactions: 54
  • Comments: 18 (3 by maintainers)

Most upvoted comments

@EkeMinusYou This is a great question. If you look at the types you’ll see that AxiosError has a property isAxiosError that is used to detect types, when combined with the builtin typeguard:

.catch((err: Error | AxiosError) {
  if (axios.isAxiosError(error))  {
    // Access to config, request, and response
  } else {
    // Just a stock error
  }
})

This is a newer feature, so I suggest you update to the newest version of Axios to ensure it is present.

I have a scenario where the error response is different than the successful response and I’m trying to figure out what’s the best way to type it. So far I came up with this but not sure it’s the best thing to do, I would appreciate your feedback 😄 https://codesandbox.io/s/upbeat-easley-ljgir?file=/src/index.ts

import request from "axios";

type TodoSuccessResponse = {
  userId: number;
  id: number;
  title: string;
  completed: boolean;
};

type TodoErrorResponse = {
  error: string;
};

async function main() {
  try {
    const res = await request.get<TodoSuccessResponse>(
      "https://jsonplaceholder.typicode.com/todos/1"
    );
    console.log(res.data.id);
  } catch (err) {
    if (request.isAxiosError(err) && err.response) {
      // Is this the correct way?
      console.log((err.response?.data as TodoErrorResponse).error);
    }
  }
}

just in case this helps anyone else I created some middleware based on this answer to make the code a bit cleaner in the eventual request:

import axios, { AxiosError } from 'axios';

interface IErrorBase<T> {
   error: Error | AxiosError<T>;
   type: 'axios-error' | 'stock-error';
}

interface IAxiosError<T> extends IErrorBase<T> {
   error: AxiosError<T>;
   type: 'axios-error';
}
interface IStockError<T> extends IErrorBase<T> {
   error: Error;
   type: 'stock-error';
}

export function axiosErrorHandler<T>(
   callback: (err: IAxiosError<T> | IStockError<T>) => void
) {
   return (error: Error | AxiosError<T>) => {
      if (axios.isAxiosError(error)) {
         callback({
            error: error,
            type: 'axios-error'
         });
      } else {
         callback({
            error: error,
            type: 'stock-error'
         });
      }
   };
}

Then the code looks like:

.catch(
   axiosErrorHandler<MyType>(res => {
      if (res.type === 'axios-error') {
         //type is available here
         const error = res.error;
      } else {
         //stock error
      }
   })
);

It worked for me:

try {
  // statements
} catch(err) {
  const errors = err as Error | AxiosError;
  if(!axios.isAxiosError(error)){
    // do whatever you want with native error
  }
  // do what you want with your axios error
}

axios.isAxiosError is useful, but it doesn’t type-guard or allow you to specify a generic response type. I added a really simple util function in my codebase to help with those:

export function isAxiosError<ResponseType>(error: unknown): error is AxiosError<ResponseType> {
  return axios.isAxiosError(error);
}

You can then use it to get fully-typed error response data:

type MyExpectedResponseType = {
  thisIsANumber: number;
};

try {
  // Make the axios fetch.
} catch (error: unknown) {
  if (isAxiosError<MyExpectedResponseType>(error)) {
    // "thisIsANumber" is properly typed here:
    console.log(error.response?.data.thisIsANumber);
  }
}

Error is defined by the typescript libs for your target (in your tsconfig.json file). For example: https://github.com/microsoft/TypeScript/blob/main/lib/lib.es5.d.ts#L1052

Revisiting this after a year for learning more about ts/js I would rewrite it as:

.catch((err: unknown) {
  if (axios.isAxiosError(error))  {
    // Access to config, request, and response
  } else if (err instanceof Error) {
    // Just a stock error
  } else {
    // unknown
  }
})

Assuming errors are of created by new Error(...) in javascript is a bad practice. You can technically throw anything. throw "a string"; is valid, unfortunately.

The browser and node.js can throw native errors that don’t extend the Error class. For example, GeolocationPositionError is not an Error as it may not contain a stack trace.

my error interface.

interface Error {
  message: string[];
  statusCode: number;
}

my catch error.

try {
} catch (err) {
  const error = err as AxiosError<Error>;
  console.log(error.response?.data.message);
}

To identify an AxiosError in the catch clause, the isAxiosError function can be used. I made a video tutorial about it: https://www.youtube.com/watch?v=NGSck4aHfeQ

axios.isAxiosError is useful, but it doesn’t type-guard or allow you to specify a generic response type. I added a really simple util function in my codebase to help with those:

export function isAxiosError<ResponseType>(error: unknown): error is AxiosError<ResponseType> {
  return axios.isAxiosError(error);
}

You can then use it to get fully-typed error response data:

type MyExpectedResponseType = {
  thisIsANumber: number;
};

try {
  // Make the axios fetch.
} catch (error: unknown) {
  if (isAxiosError<MyExpectedResponseType>(error)) {
    // "thisIsANumber" is properly typed here:
    console.log(error.response?.data.thisIsANumber);
  }
}

I am having a little problem understanding this. MyExpectedResponseType is the expected data type to get back from a successful response? Why do we want to access it in a failed response. I thought response.data would hold information on why it failed. For example data submitted to request was incorrect and it responds with which data field failed. That would be a different type to say the data type of a successful response

I would love something more concise, like axios.get<ResponseDataType, ErrorType> to allow types to just flow through?

Looks like i need to upgrade axios package

@EkeMinusYou This is a great question. If you look at the types you’ll see that AxiosError has a property isAxiosError that is used to detect types, when combined with the builtin typeguard:

.catch((err: Error | AxiosError) {
  if (axios.isAxiosError(error))  {
    // Access to config, request, and response
  } else {
    // Just a stock error
  }
})

This is a newer feature, so I suggest you update to the newest version of Axios to ensure it is present.

Hi there @timemachine3030, What is the Error type? A type you created?

axios.isAxiosError is useful, but it doesn’t type-guard or allow you to specify a generic response type. I added a really simple util function in my codebase to help with those:

export function isAxiosError<ResponseType>(error: unknown): error is AxiosError<ResponseType> {
  return axios.isAxiosError(error);
}

You can then use it to get fully-typed error response data:

type MyExpectedResponseType = {
  thisIsANumber: number;
};

try {
  // Make the axios fetch.
} catch (error: unknown) {
  if (isAxiosError<MyExpectedResponseType>(error)) {
    // "thisIsANumber" is properly typed here:
    console.log(error.response?.data.thisIsANumber);
  }
}

I am having a little problem understanding this. MyExpectedResponseType is the expected data type to get back from a successful response? Why do we want to access it in a failed response. I thought response.data would hold information on why it failed. For example data submitted to request was incorrect and it responds with which data field failed. That would be a different type to say the data type of a successful response

I’d guess that this is useful in the case of known/expected types of Axios errors. For example, I use the following to provide type info to Laravel validation error responses (always status code 422 with message & errors data properties):

interface LaravelValidationResponse extends AxiosResponse {
    status: 422;
    data: {
        message: string;
        errors: Record<string, Array<string>>;
    };
}

export interface LaravelValidationError extends AxiosError {
    response: LaravelValidationResponse;
}

function axiosResponseIsLaravelValidationResponse(response: AxiosResponse): response is LaravelValidationResponse {
    return response.status === 422
        && typeof response.data?.message === 'string'
        && typeof response.data?.errors === 'object';
}

export function isLaravelValidationError(error: unknown): error is LaravelValidationError {
    return Boolean(
        axios.isAxiosError(error)
        && error.response
        && axiosResponseIsLaravelValidationResponse(error.response)
    );
}

While I probably wouldn’t go with @btraut’s approach as it doesn’t allow for differentiating between different types of errors, it could be useful as a quick type cast.

` import axios, { AxiosError } from “axios”

try {

  const { data } = await axios.get<MyTypeReturn>(requestURI, {
    headers: {
      Authorization: `Bearer ${FEDERAL_TOKEN}`
    }
  })

  return data

} catch (error) {

  let message = "Generic message error"
  let code = 500

  if (error instanceof AxiosError) {
    message = error.response?.data.message || "Server Unavailable"
    code = error.response?.status || 503
  }

  if (error instanceof Error) {
    message = error.message
  }
}

`