Axios error response 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...

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and
privacy statement. We’ll occasionally send you account related emails.

Already on GitHub?
Sign in
to your account


Closed

EkeMinusYou opened this issue

Jan 30, 2021

· 13 comments

Comments

@EkeMinusYou

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.

panlina, fredarend, cdpark0530, Eusebiotrigo, fariasmateuss, CharleeWa, davidatsurge, AdisonCavani, adwhit, urakagi, and 15 more reacted with thumbs up emoji
quandinhphan reacted with thumbs down emoji

@timemachine3030

@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.

pacexy, enkelmedia, cer-vic, luistak, locofocos, gustavomdeiros, ckcr4lyf, Cleberw3b, jusevasa, wobsoriano, and 289 more reacted with thumbs up emoji
luistak, budasnorbi, slava-arapov, demostheneslld, iamrobins, muka, dikshit-leora, Luis-GiraldoM, lucasousi, martinmcneela, and 17 more reacted with hooray emoji
RomanKornev, lucasousi, martinmcneela, dngwoodo, euclidesdry, libeyondea, sou-gabriel, MinskLeo, bsor-dev, dovranJorayev, and dagorod reacted with confused emoji
luistak, pablodicosta, pitfritsch, cuzzlor, filev-svt, RSchneider94, demostheneslld, alefduarte, iamrobins, hummli, and 39 more reacted with heart emoji
luistak, michaeloliverx, demostheneslld, iamrobins, svlipatnikov, dikshit-leora, AmitThakur11, jtreeves, Luis-GiraldoM, sametbalta, and 19 more reacted with rocket emoji

@EkeMinusYou

@m4ttheweric

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
      }
   })
);
YerzhanUtelbayev, vincerubinetti, aseemgautam, BurgerBurglar, brnohxz, alexanderomnix, jsc7727, alexisveryreal, koji, ioandev, and 28 more reacted with thumbs up emoji

@mtt87

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);
    }
  }
}
Grawl, johnGachihi, PawaOx4th, zell0, neldeles, edsilvaseekout, brnohxz, vitorpedeo, SammuelGR, milenahikari, and 29 more reacted with thumbs up emoji

@askariacc

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
}
brenonovelli, tolumide-ng, taiyrbegeyev, hassonor, I-am-abdulazeez, DhansAL, livimonte, techandmedia, honia19, humleflue, and 17 more reacted with thumbs up emoji
Eitol and blazity-bmstefanski reacted with thumbs down emoji
I-am-abdulazeez and DhansAL reacted with hooray emoji
itslewisndungu, nik32, jvvoliveira, and trey-wallis reacted with heart emoji

@Enfield-Li

Optionally use //@ts-ignore for a easy pass.

AquiGorka, petrokss, oleksii-salimonenko-cloudeou, bilalbutt044, and OleksandrBasiukZap reacted with thumbs up emoji
raajnadar, PabloDinella, bauertill, elvizcacho, Marinell0, fariasmateuss, rametta, EsdrasXavier, Lawlzer, mikedelcastillo, and 134 more reacted with thumbs down emoji
danbower, igoramos77, dboscanv, CharleeWa, sledz-mobtex, oahmaro, rismailov, David-L-R, nik32, Almeida154, and 16 more reacted with laugh emoji
AquiGorka and bilalbutt044 reacted with hooray emoji
cnguyen-de, jmz7v, fariasmateuss, wozitto, oahmaro, ammaroker-r, Nelie-Taylor, SimonHausdorf, and patelnets reacted with confused emoji
AquiGorka, A-Baptiste, and bilalbutt044 reacted with heart emoji
AquiGorka reacted with rocket emoji
AquiGorka reacted with eyes emoji

@btraut

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);
  }
}
LeeManh and chiptus reacted with thumbs up emoji
HMoen reacted with hooray emoji
askariacc, muhrizqiardi, 1majek, LautaroRiveiro, eboody, andrewvasilchuk, dominikalexanderlise, MattyMags, kingyong9169, MichalPniak, and 8 more reacted with heart emoji

@janusqa

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

@glspdotnet

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.

@damisparks

@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.

This is fantastic and very helpful. Thank you very much 🙏🏾

@RefaelAm

@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?

@timemachine3030

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.

@Felipeex

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);
}

Содержание

  1. Axios & Error handling like a boss 😎
  2. Introduction
  3. The Problem
  4. The very ugly solution
  5. The elegant and recommended solution
  6. The IMPROVED and elegant solution
  7. Customize for one http call
  8. The Final Thoughts
  9. How to type axios error in Typescript? #3612
  10. Comments
  11. Describe the issue
  12. Example Code
  13. Expected behavior, if applicable
  14. Environment
  15. Additional context/Screenshots
  16. Axios error response typescript
  17. Making Http POST requests with Axios in TypeScript #
  18. Making Http PATCH requests with Axios in TypeScript #
  19. Making Http PUT requests with Axios in TypeScript #
  20. Making Http DELETE requests with Axios in TypeScript #

Axios & Error handling like a boss 😎

Introduction

I really love the problem/solution. approach. We see some problem, and then, a really nice solution. But for this talking, i think we need some introduction as well.

When you develop an web application, you generally want’s to separate the frontend and backend. Fo that, you need something that makes the communication between these guys.

To illustrate, you can build a frontend (commonly named as GUI or user interface) using vanilla HTML, CSS and Javascript, or, frequently, using several frameworks like Vue, React and so many more avaliable online. I marked Vue because it’s my personal preference.

Why? I really don’t study the others so deeply that i can’t assure to you that Vue is the best, but i liked the way he works, the syntax, and so on. It’s like your crush, it’s a personal choice.

But, beside that, any framework you use, you will face the same problem:_ How to communicate with you backend_ (that can be written in so many languages, that i will not dare mention some. My current crush? Python an Flask).

One solution is to use AJAX (What is AJAX? Asynchronous JavaScript And XML). You can use XMLHttpRequest directly, to make requests to backend and get the data you need, but the downside is that the code is verbose. You can use Fetch API that will make an abstraction on top of XMLHttpRequest , with a powerfull set of tools. Other great change is that Fetch API will use Promises, avoiding the callbacks from XMLHttpRequest (preventing the callback hell).

Alternatively, we have a awesome library named Axios, that have a nice API (for curiosity purposes, under the hood, uses XMLHttpRequest , giving a very wide browser support). The Axios API wraps the XMLHttpRequest into Promises , different from Fetch API . Beside that, nowadays Fetch API is well supported by the browsers engines available, and have polyfills for older browsers. I will not discuss which one is better because i really think is personal preference, like any other library or framework around. If you dont’t have an opinion, i suggest that you seek some comparisons and dive deep articles. Has a nice article that i will mention to you written by Faraz Kelhini.

My personal choice is Axios because have a nice API, has Response timeout, automatic JSON transformation, and Interceptors (we will use them in the proposal solution), and so much more. Nothing that cannot be accomplished by Fetch API , but has another approach.

The Problem

Talking about Axios , a simple GET HTTP request can be made with these lines of code:

Exit fullscreen mode

We’ve used Typescript (interfaces, and generics), ES6 Modules, Promises, Axios and Arrow Functions. We will not touch them deeply, and will presume that you already know about them.

So, in the above code, if everything goes well, aka: the server is online, the network is working perfectly, so on, when you run this code you will see the list of users on console. The real life isn’t always perfect.

We, developers, have a mission:

Make the life of users simple!

So, when something is go bad, we need to use all the efforts in ours hands to resolve the problem ourselves, without the user even notice, and, when nothing more can be done, we have the obligation to show them a really nice message explaining what goes wrong, to easy theirs souls.

Axios like Fetch API uses Promises to handle asynchronous calls and avoid the callbacks that we mention before. Promises are a really nice API and not to difficult to understand. We can chain actions ( then ) and error handlers ( catch ) one after another, and the API will call them in order. If an Error occurs in the Promise, the nearest catch is found and executed.

So, the code above with basic error handler will become:

Exit fullscreen mode

Ok, and what is the problem then? Well, we have a hundred errors that, in every API call, the solution/message is the same. For curiosity, Axios show us a little list of them: ERR_FR_TOO_MANY_REDIRECTS, ERR_BAD_OPTION_VALUE, ERR_BAD_OPTION, ERR_NETWORK, ERR_DEPRECATED, ERR_BAD_RESPONSE, ERR_BAD_REQUEST, ERR_CANCELED, ECONNABORTED, ETIMEDOUT . We have the HTTP Status Codes, where we found so many errors, like 404 (Page Not Found), and so on. You get the picture. We have too much common errors to elegantly handle in every API request.

The very ugly solution

One very ugly solution that we can think of, is to write one big ass function that we increment every new error we found. Besides the ugliness of this approach, it will work, if you and your team remember to call the function in every API request.

Exit fullscreen mode

With our magical badass function in place, we can use it like that:

Exit fullscreen mode

We have to remember to add this catch in every API call, and, for every new error that we can graciously handle, we need to increase our nasty httpErrorHandler with some more code and ugly if’s .

Other problem we have with this approach, besides ugliness and lack of mantenability, is that, if in one, only single one API call, i desire to handle different from global approach, i cannot do.

The function will grow exponentially as the problems that came together. This solution will not scale right!

The elegant and recommended solution

When we work as a team, to make them remember the slickness of every piece of software is hard, very hard. Team members, come and go, and i do not know any documentation good enough to surpass this issue.

In other hand, if the code itself can handle these problems on a generic way, do-it! The developers cannot make mistakes if they need do nothing!

Before we jump into code (that is what we expect from this article), i have the need to speak some stuff to you understand what the codes do.

Axios allow we to use something called Interceptors that will be executed in every request you make. It’s a awesome way of checking permission, add some header that need to be present, like a token, and preprocess responses, reducing the amount of boilerplate code.

We have two types of Interceptors . Before (request) and After (response) an AJAX Call.

It’s use is simple as that:

Exit fullscreen mode

But, in this article, we will use the response interceptor, because is where we want to deal with errors. Nothing stops you to extend the solution to handle request errors as well.

An simple use of response interceptor, is to call ours big ugly function to handle all sort of errors.

As every form of automatic handler, we need a way to bypass this (disable), when we want. We are gonna extend the AxiosRequestConfig interface and add two optional options raw and silent . If raw is set to true , we are gonna do nothing. silent is there to mute notifications that we show when dealing with global errors.

Exit fullscreen mode

Next step is to create a Error class that we will throw every time we want to inform the error handler to assume the problem.

Exit fullscreen mode

Now, let’s write the interceptors:

Exit fullscreen mode

Well, we do not need to remember our magical badass function in every ajax call we made. And, we can disable when we want, just passing raw to request config.

Exit fullscreen mode

Ok, this is a nice solution, but, this bad-ass ugly function will grow so much, that we cannot see the end. The function will become so big, that anyone will want to maintain.

Can we improve more? Oh yeahhh.

The IMPROVED and elegant solution

We are gonna develop an Registry class, using Registry Design Pattern. The class will allow you to register error handling by an key (we will deep dive in this in a moment) and a action, that can be an string (message), an object (that can do some nasty things) or an function, that will be executed when the error matches the key. The registry will have parent that can be placed to allow you override keys to custom handle scenarios.

Here are some types that we will use througth the code:

Exit fullscreen mode

So, with types done, let’s see the class implementation. We are gonna use an Map to store object/keys and a parent, that we will seek if the key is not found in the current class. If parent is null, the search will end. On construction, we can pass an parent,and optionally, an instance of ErrorHandlerMany , to register some handlers.

Exit fullscreen mode

Let’s deep dive the resposeErrorHandler code. We choose to use key as an identifier to select the best handler for error. When you look at the code, you see that has an order that key will be searched in the registry. The rule is, search for the most specific to the most generic.

Exit fullscreen mode

This is an example of an error sent by API:

Exit fullscreen mode

Other example, as well:

Exit fullscreen mode

So, as an example, we can now register ours generic error handling:

Exit fullscreen mode

We can register error handler in any place we like, group the most generic in one typescript file, and specific ones inline. You choose. But, to this work, we need to attach to ours http axios instance. This is done like this:

Exit fullscreen mode

Now, we can make ajax requests, and the error handler will work as expected:

Exit fullscreen mode

The code above will show a Notify ballon on the user screen, because will fire the 404 error status code, that we registered before.

Customize for one http call

The solution doesn’t end here. Let’s assume that, in one, only one http request, you want to handle 404 differently, but just 404 . For that, we create the dealsWith function below:

Exit fullscreen mode

This function uses the ErrorHandlerRegistry parent to personalize one key, but for all others, use the global handlers (if you wanted that, ignoreGlobal is there to force not).

So, we can write code like this:

Exit fullscreen mode

The Final Thoughts

All this explanation is nice, but code, ah, the code, is so much better. So, i’ve created an github repository with all code from this article organized to you try out, improve and customize.

  • This post became so much bigger than a first realize, but i love to share my thoughts.
  • If you have some improvement to the code, please let me know in the comments.
  • If you see something wrong, please, fix-me!

Источник

How to type axios error in Typescript? #3612

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

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.

The text was updated successfully, but these errors were encountered:

@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:

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

@timemachine3030 It looks good. Thank you!

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:

Then the code looks like:

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

It worked for me:

Optionally use //@ts-ignore for a easy pass.

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:

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

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:

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

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

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:

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

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):

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.

Источник

Axios error response typescript

Make sure to install axios before using the code snippets in this article.

You can install axios by opening your terminal in your project’s root directory and running the npm install axios command.

Here is an example of an HTTP GET request using axios in TypeScript.

We defined the type for the response we expect from the server and provided it when using the axios.get method.

As you can see, the first argument we passed to the axios.get() method is the url.

The second argument is a request config object and is not required.

If you need to check the response schema, look at this section of the axios GitHub repository.

We destructured the data property from the axios response schema.

The data property is the response that was provided by the server.

Axios includes a type guard for errors, which we used in our catch method.

If the error is an axios error, we can safely use the message property to get the error message. Otherwise, the error is typed as unknown and we have to manually check its type before accessing any properties.

Making Http POST requests with Axios in TypeScript #

Let’s look at an example HTTP POST request made with axios in TypeScript.

I’ll post the entire code snippet and then we’ll go over it.

We used the axios.post method to make an HTTP POST request.

We passed the type of the expected response as a generic to the axios.post() method.

We don’t have to pass the request body to the JSON.stringify() method as axios automatically takes care of this.

We included an object containing the request headers. If your remote API requires authentication, you might need to set an Authorization header that points to a json web token.

You can look at the other possible values in the request config by visiting the axios GitHub repository.

Making Http PATCH requests with Axios in TypeScript #

Let’s look at an example HTTP PATCH request made with axios in TypeScript.

The axios.patch method is very similar to axios.post — it takes the exact same 3 parameters:

  1. The url (the server url that will be used for the request)
  2. The request body
  3. The request config object

Making Http PUT requests with Axios in TypeScript #

For completeness sake, let’s look at an example HTTP PUT request made with axios in TypeScript.

I’ll post the entire code snippet and then we’ll go over it.

The only thing that changed in the code snippet is that instead of using axios.patch , we are now using axios.put.

Making Http DELETE requests with Axios in TypeScript #

Let’s look at an example HTTP DELETE request made with axios in TypeScript.

The API I’m using in the examples sends an empty string as a response for an HTTP delete request.

Источник

Introduction

I really love the problem/solution. approach. We see some problem, and then, a really nice solution. But for this talking, i think we need some introduction as well.

When you develop an web application, you generally want’s to separate the frontend and backend. Fo that, you need something that makes the communication between these guys.

To illustrate, you can build a frontend (commonly named as GUI or user interface) using vanilla HTML, CSS and Javascript, or, frequently, using several frameworks like Vue, React and so many more avaliable online. I marked Vue because it’s my personal preference.

Why? I really don’t study the others so deeply that i can’t assure to you that Vue is the best, but i liked the way he works, the syntax, and so on. It’s like your crush, it’s a personal choice.

But, beside that, any framework you use, you will face the same problem:_ How to communicate with you backend_ (that can be written in so many languages, that i will not dare mention some. My current crush? Python an Flask).

One solution is to use AJAX (What is AJAX? Asynchronous JavaScript And XML). You can use XMLHttpRequest directly, to make requests to backend and get the data you need, but the downside is that the code is verbose. You can use Fetch API that will make an abstraction on top of XMLHttpRequest, with a powerfull set of tools. Other great change is that Fetch API will use Promises, avoiding the callbacks from XMLHttpRequest (preventing the callback hell).

Alternatively, we have a awesome library named Axios, that have a nice API (for curiosity purposes, under the hood, uses XMLHttpRequest, giving a very wide browser support). The Axios API wraps the XMLHttpRequest into Promises, different from Fetch API. Beside that, nowadays Fetch API is well supported by the browsers engines available, and have polyfills for older browsers. I will not discuss which one is better because i really think is personal preference, like any other library or framework around. If you dont’t have an opinion, i suggest that you seek some comparisons and dive deep articles. Has a nice article that i will mention to you written by Faraz Kelhini.

My personal choice is Axios because have a nice API, has Response timeout, automatic JSON transformation, and Interceptors (we will use them in the proposal solution), and so much more. Nothing that cannot be accomplished by Fetch API, but has another approach.

The Problem

Talking about Axios, a simple GET HTTP request can be made with these lines of code:

import axios from 'axios'

//here we have an generic interface with basic structure of a api response:
interface HttpResponse<T> {
  data: T[]
}

// the user interface, that represents a user in the system
interface User {
  id: number
  email: string
  name: string
}

//the http call to Axios
axios.get<HttpResponse<User>>('/users').then((response) => {
  const userList = response.data
  console.log(userList)
})

Enter fullscreen mode

Exit fullscreen mode

We’ve used Typescript (interfaces, and generics), ES6 Modules, Promises, Axios and Arrow Functions. We will not touch them deeply, and will presume that you already know about them.

So, in the above code, if everything goes well, aka: the server is online, the network is working perfectly, so on, when you run this code you will see the list of users on console. The real life isn’t always perfect.

We, developers, have a mission:

Make the life of users simple!

So, when something is go bad, we need to use all the efforts in ours hands to resolve the problem ourselves, without the user even notice, and, when nothing more can be done, we have the obligation to show them a really nice message explaining what goes wrong, to easy theirs souls.

Axios like Fetch API uses Promises to handle asynchronous calls and avoid the callbacks that we mention before. Promises are a really nice API and not to difficult to understand. We can chain actions (then) and error handlers (catch) one after another, and the API will call them in order. If an Error occurs in the Promise, the nearest catch is found and executed.

So, the code above with basic error handler will become:

import axios from 'axios'

//..here go the types, equal above sample.

//here we call axios and passes generic get with HttpResponse<User>.
axios
  .get<HttpResponse<User>>('/users')
  .then((response) => {
    const userList = response.data
    console.log(userList)
  })
  .catch((error) => {
    //try to fix the error or
    //notify the users about somenthing went wrong
    console.log(error.message)
  })

Enter fullscreen mode

Exit fullscreen mode

Ok, and what is the problem then? Well, we have a hundred errors that, in every API call, the solution/message is the same. For curiosity, Axios show us a little list of them: ERR_FR_TOO_MANY_REDIRECTS, ERR_BAD_OPTION_VALUE, ERR_BAD_OPTION, ERR_NETWORK, ERR_DEPRECATED, ERR_BAD_RESPONSE, ERR_BAD_REQUEST, ERR_CANCELED, ECONNABORTED, ETIMEDOUT. We have the HTTP Status Codes, where we found so many errors, like 404 (Page Not Found), and so on. You get the picture. We have too much common errors to elegantly handle in every API request.

The very ugly solution

One very ugly solution that we can think of, is to write one big ass function that we increment every new error we found. Besides the ugliness of this approach, it will work, if you and your team remember to call the function in every API request.

function httpErrorHandler(error) {
  if (error === null) throw new Error('Unrecoverable error!! Error is null!')
  if (axios.isAxiosError(error)) {
    //here we have a type guard check, error inside this if will be treated as AxiosError
    const response = error?.response
    const request = error?.request
    const config = error?.config //here we have access the config used to make the api call (we can make a retry using this conf)

    if (error.code === 'ERR_NETWORK') {
      console.log('connection problems..')
    } else if (error.code === 'ERR_CANCELED') {
      console.log('connection canceled..')
    }
    if (response) {
      //The request was made and the server responded with a status code that falls out of the range of 2xx the http status code mentioned above
      const statusCode = response?.status
      if (statusCode === 404) {
        console.log('The requested resource does not exist or has been deleted')
      } else if (statusCode === 401) {
        console.log('Please login to access this resource')
        //redirect user to login
      }
    } else if (request) {
      //The request was made but no response was received, `error.request` is an instance of XMLHttpRequest in the browser and an instance of http.ClientRequest in Node.js
    }
  }
  //Something happened in setting up the request and triggered an Error
  console.log(error.message)
}

Enter fullscreen mode

Exit fullscreen mode

With our magical badass function in place, we can use it like that:

import axios from 'axios'

axios
  .get('/users')
  .then((response) => {
    const userList = response.data
    console.log(userList)
  })
  .catch(httpErrorHandler)

Enter fullscreen mode

Exit fullscreen mode

We have to remember to add this catch in every API call, and, for every new error that we can graciously handle, we need to increase our nasty httpErrorHandler with some more code and ugly if's.

Other problem we have with this approach, besides ugliness and lack of mantenability, is that, if in one, only single one API call, i desire to handle different from global approach, i cannot do.

The function will grow exponentially as the problems that came together. This solution will not scale right!

The elegant and recommended solution

When we work as a team, to make them remember the slickness of every piece of software is hard, very hard. Team members, come and go, and i do not know any documentation good enough to surpass this issue.

In other hand, if the code itself can handle these problems on a generic way, do-it! The developers cannot make mistakes if they need do nothing!

Before we jump into code (that is what we expect from this article), i have the need to speak some stuff to you understand what the codes do.

Axios allow we to use something called Interceptors that will be executed in every request you make. It’s a awesome way of checking permission, add some header that need to be present, like a token, and preprocess responses, reducing the amount of boilerplate code.

We have two types of Interceptors. Before (request) and After (response) an AJAX Call.

It’s use is simple as that:

//Intercept before request is made, usually used to add some header, like an auth
const axiosDefaults = {}
const http = axios.create(axiosDefaults)
//register interceptor like this
http.interceptors.request.use(
  function (config) {
    // Do something before request is sent
    const token = window.localStorage.getItem('token') //do not store token on localstorage!!!
    config.headers.Authorization = token
    return config
  },
  function (error) {
    // Do something with request error
    return Promise.reject(error)
  }
)

Enter fullscreen mode

Exit fullscreen mode

But, in this article, we will use the response interceptor, because is where we want to deal with errors. Nothing stops you to extend the solution to handle request errors as well.

An simple use of response interceptor, is to call ours big ugly function to handle all sort of errors.

As every form of automatic handler, we need a way to bypass this (disable), when we want. We are gonna extend the AxiosRequestConfig interface and add two optional options raw and silent. If raw is set to true, we are gonna do nothing. silent is there to mute notifications that we show when dealing with global errors.

declare module 'axios' {
  export interface AxiosRequestConfig {
    raw?: boolean
    silent?: boolean
  }
}

Enter fullscreen mode

Exit fullscreen mode

Next step is to create a Error class that we will throw every time we want to inform the error handler to assume the problem.

export class HttpError extends Error {
  constructor(message?: string) {
    super(message) // 'Error' breaks prototype chain here
    this.name = 'HttpError'
    Object.setPrototypeOf(this, new.target.prototype) // restore prototype chain
  }
}

Enter fullscreen mode

Exit fullscreen mode

Now, let’s write the interceptors:

// this interceptor is used to handle all success ajax request
// we use this to check if status code is 200 (success), if not, we throw an HttpError
// to our error handler take place.
function responseHandler(response: AxiosResponse<any>) {
  const config = response?.config
  if (config.raw) {
    return response
  }
  if (response.status == 200) {
    const data = response?.data
    if (!data) {
      throw new HttpError('API Error. No data!')
    }
    return data
  }
  throw new HttpError('API Error! Invalid status code!')
}

function responseErrorHandler(response) {
  const config = response?.config
  if (config.raw) {
    return response
  }
  // the code of this function was written in above section.
  return httpErrorHandler(response)
}

//Intercept after response, usually to deal with result data or handle ajax call errors
const axiosDefaults = {}
const http = axios.create(axiosDefaults)
//register interceptor like this
http.interceptors.response.use(responseHandler, responseErrorHandler)

Enter fullscreen mode

Exit fullscreen mode

Well, we do not need to remember our magical badass function in every ajax call we made. And, we can disable when we want, just passing raw to request config.

import axios from 'axios'

// automagically handle error
axios
  .get('/users')
  .then((response) => {
    const userList = response.data
    console.log(userList)
  })
  //.catch(httpErrorHandler) this is not needed anymore

// to disable this automatic error handler, pass raw
axios
  .get('/users', {raw: true})
  .then((response) => {
    const userList = response.data
    console.log(userList)
  }).catch(() {
    console.log("Manually handle error")
  })

Enter fullscreen mode

Exit fullscreen mode

Ok, this is a nice solution, but, this bad-ass ugly function will grow so much, that we cannot see the end. The function will become so big, that anyone will want to maintain.

Can we improve more? Oh yeahhh.

The IMPROVED and elegant solution

We are gonna develop an Registry class, using Registry Design Pattern. The class will allow you to register error handling by an key (we will deep dive in this in a moment) and a action, that can be an string (message), an object (that can do some nasty things) or an function, that will be executed when the error matches the key. The registry will have parent that can be placed to allow you override keys to custom handle scenarios.

Here are some types that we will use througth the code:

// this interface is the default response data from ours api
interface HttpData {
  code: string
  description?: string
  status: number
}

// this is all errrors allowed to receive
type THttpError = Error | AxiosError | null

// object that can be passed to our registy
interface ErrorHandlerObject {
  after?(error?: THttpError, options?: ErrorHandlerObject): void
  before?(error?: THttpError, options?: ErrorHandlerObject): void
  message?: string
  notify?: QNotifyOptions
}

//signature of error function that can be passed to ours registry
type ErrorHandlerFunction = (error?: THttpError) => ErrorHandlerObject | boolean | undefined

//type that our registry accepts
type ErrorHandler = ErrorHandlerFunction | ErrorHandlerObject | string

//interface for register many handlers once (object where key will be presented as search key for error handling
interface ErrorHandlerMany {
  [key: string]: ErrorHandler
}

// type guard to identify that is an ErrorHandlerObject
function isErrorHandlerObject(value: any): value is ErrorHandlerObject {
  if (typeof value === 'object') {
    return ['message', 'after', 'before', 'notify'].some((k) => k in value)
  }
  return false
}

Enter fullscreen mode

Exit fullscreen mode

So, with types done, let’s see the class implementation. We are gonna use an Map to store object/keys and a parent, that we will seek if the key is not found in the current class. If parent is null, the search will end. On construction, we can pass an parent,and optionally, an instance of ErrorHandlerMany, to register some handlers.

class ErrorHandlerRegistry {
  private handlers = new Map<string, ErrorHandler>()

  private parent: ErrorHandlerRegistry | null = null

  constructor(parent: ErrorHandlerRegistry = undefined, input?: ErrorHandlerMany) {
    if (typeof parent !== 'undefined') this.parent = parent
    if (typeof input !== 'undefined') this.registerMany(input)
  }

  // allow to register an handler
  register(key: string, handler: ErrorHandler) {
    this.handlers.set(key, handler)
    return this
  }

  // unregister a handler
  unregister(key: string) {
    this.handlers.delete(key)
    return this
  }

  // search a valid handler by key
  find(seek: string): ErrorHandler | undefined {
    const handler = this.handlers.get(seek)
    if (handler) return handler
    return this.parent?.find(seek)
  }

  // pass an object and register all keys/value pairs as handler.
  registerMany(input: ErrorHandlerMany) {
    for (const [key, value] of Object.entries(input)) {
      this.register(key, value)
    }
    return this
  }

  // handle error seeking for key
  handleError(
    this: ErrorHandlerRegistry,
    seek: (string | undefined)[] | string,
    error: THttpError
  ): boolean {
    if (Array.isArray(seek)) {
      return seek.some((key) => {
        if (key !== undefined) return this.handleError(String(key), error)
      })
    }
    const handler = this.find(String(seek))
    if (!handler) {
      return false
    } else if (typeof handler === 'string') {
      return this.handleErrorObject(error, { message: handler })
    } else if (typeof handler === 'function') {
      const result = handler(error)
      if (isErrorHandlerObject(result)) return this.handleErrorObject(error, result)
      return !!result
    } else if (isErrorHandlerObject(handler)) {
      return this.handleErrorObject(error, handler)
    }
    return false
  }

  // if the error is an ErrorHandlerObject, handle here
  handleErrorObject(error: THttpError, options: ErrorHandlerObject = {}) {
    options?.before?.(error, options)
    showToastError(options.message ?? 'Unknown Error!!', options, 'error')
    return true
  }

  // this is the function that will be registered in interceptor.
  resposeErrorHandler(this: ErrorHandlerRegistry, error: THttpError, direct?: boolean) {
    if (error === null) throw new Error('Unrecoverrable error!! Error is null!')
    if (axios.isAxiosError(error)) {
      const response = error?.response
      const config = error?.config
      const data = response?.data as HttpData
      if (!direct && config?.raw) throw error
      const seekers = [
        data?.code,
        error.code,
        error?.name,
        String(data?.status),
        String(response?.status),
      ]
      const result = this.handleError(seekers, error)
      if (!result) {
        if (data?.code && data?.description) {
          return this.handleErrorObject(error, {
            message: data?.description,
          })
        }
      }
    } else if (error instanceof Error) {
      return this.handleError(error.name, error)
    }
    //if nothings works, throw away
    throw error
  }
}
// create ours globalHandlers object
const globalHandlers = new ErrorHandlerRegistry()

Enter fullscreen mode

Exit fullscreen mode

Let’s deep dive the resposeErrorHandler code. We choose to use key as an identifier to select the best handler for error. When you look at the code, you see that has an order that key will be searched in the registry. The rule is, search for the most specific to the most generic.

const seekers = [
  data?.code, //Our api can send an error code to you personalize the error messsage.
  error.code, //The AxiosError has an error code too (ERR_BAD_REQUEST is one).
  error?.name, //Error has a name (class name). Example: HttpError, etc..
  String(data?.status), //Our api can send an status code as well.
  String(response?.status), //respose status code. Both based on Http Status codes.
]

Enter fullscreen mode

Exit fullscreen mode

This is an example of an error sent by API:

{
  "code": "email_required",
  "description": "An e-mail is required",
  "error": true,
  "errors": [],
  "status": 400
}

Enter fullscreen mode

Exit fullscreen mode

Other example, as well:

{
  "code": "no_input_data",
  "description": "You doesnt fill input fields!",
  "error": true,
  "errors": [],
  "status": 400
}

Enter fullscreen mode

Exit fullscreen mode

So, as an example, we can now register ours generic error handling:

globalHandlers.registerMany({
  //this key is sent by api when login is required
  login_required: {
    message: 'Login required!',
    //the after function will be called when the message hides.
    after: () => console.log('redirect user to /login'),
  },
  no_input_data: 'You must fill form values here!',
  //this key is sent by api on login error.
  invalid_login: {
    message: 'Invalid credentials!',
  },
  '404': { message: 'API Page Not Found!' },
  ERR_FR_TOO_MANY_REDIRECTS: 'Too many redirects.',
})

// you can registre only one:
globalHandlers.register('HttpError', (error) => {
  //send email to developer that api return an 500 server internal console.error
  return { message: 'Internal server errror! We already notify developers!' }
  //when we return an valid ErrorHandlerObject, will be processed as whell.
  //this allow we to perform custom behavior like sending email and default one,
  //like showing an message to user.
})

Enter fullscreen mode

Exit fullscreen mode

We can register error handler in any place we like, group the most generic in one typescript file, and specific ones inline. You choose. But, to this work, we need to attach to ours http axios instance. This is done like this:

function createHttpInstance() {
  const instance = axios.create({})
  const responseError = (error: any) => globalHandlers.resposeErrorHandler(error)
  instance.interceptors.response.use(responseHandler, responseError)
  return instance
}

export const http: AxiosInstance = createHttpInstance()

Enter fullscreen mode

Exit fullscreen mode

Now, we can make ajax requests, and the error handler will work as expected:

import http from '/src/modules/http'

// automagically handle error
http.get('/path/that/dont/exist').then((response) => {
  const userList = response.data
  console.log(userList)
})

Enter fullscreen mode

Exit fullscreen mode

The code above will show a Notify ballon on the user screen, because will fire the 404 error status code, that we registered before.

Customize for one http call

The solution doesn’t end here. Let’s assume that, in one, only one http request, you want to handle 404 differently, but just 404. For that, we create the dealsWith function below:

export function dealWith(solutions: ErrorHandlerMany, ignoreGlobal?: boolean) {
  let global
  if (ignoreGlobal === false) global = globalHandlers
  const localHandlers = new ErrorHandlerRegistry(global, solutions)
  return (error: any) => localHandlers.resposeErrorHandler(error, true)
}

Enter fullscreen mode

Exit fullscreen mode

This function uses the ErrorHandlerRegistry parent to personalize one key, but for all others, use the global handlers (if you wanted that, ignoreGlobal is there to force not).

So, we can write code like this:

import http from '/src/modules/http'

// this call will show the message 'API Page Not Found!'
http.get('/path/that/dont/exist')

// this will show custom message: 'Custom 404 handler for this call only'
// the raw is necessary because we need to turn off the global handler.
http.get('/path/that/dont/exist', { raw: true }).catch(
  dealsWith({
    404: { message: 'Custom 404 handler for this call only' },
  })
)

// we can turn off global, and handle ourselves
// if is not the error we want, let the global error take place.
http
  .get('/path/that/dont/exist', { raw: true })
  .catch((e) => {
    //custom code handling
    if (e.name == 'CustomErrorClass') {
      console.log('go to somewhere')
    } else {
      throw e
    }
  })
  .catch(
    dealsWith({
      404: { message: 'Custom 404 handler for this call only' },
    })
  )

Enter fullscreen mode

Exit fullscreen mode

The Final Thoughts

All this explanation is nice, but code, ah, the code, is so much better. So, i’ve created an github repository with all code from this article organized to you try out, improve and customize.

  • Click here to access the repo in github.

FOOTNOTES:

  • This post became so much bigger than a first realize, but i love to share my thoughts.
  • If you have some improvement to the code, please let me know in the comments.
  • If you see something wrong, please, fix-me!

I have an API route (using Next.js API routes) that has a certain response shape when successful and another when an error occurs.

export type ApiErrorResponse = { message: string };

export type SuccessResponse = {
  user: UserSession | null;
};

export type GetCurrentUserHandlerResponse = ApiErrorResponse | SuccessResponse;

const getCurrentUserHandler: NextApiHandler<GetCurrentUserHandlerResponse> = () => { /* ... */ };

How can I tell Axios what the shape of the response is depending on the error?

When I abstract away the request like this:

const getCurrentUserRoute = '/api/user';

const getCurrentUserRequest = () =>
  axios.get<GetCurrentUserHandlerResponse>(getCurrentUserRoute);

and use getCurrentUserRequest I always have to distinguish in both .then and in .catch whether the response is of the success shape, or of the error shape.

Ideally I’m looking for something like this:

const getCurrentUserRequest = () =>
  axios.get<SuccessResponse, ApiErrorResponse>(getCurrentUserRoute);

Ultimately I want TypeScript to be able to do this:

getCurrentUserRequest()
  .then((response) => {
    // now TS knows response.data is of the shape { user: UserSession | null; }
  })
  .catch((error) => {
    // now TS knows error.response.data is { message: string; }
  });

Is there a way to do that with TypeScript?

Making API calls is integral to most applications and while doing this we use an HTTP client usually available as an external library. Axios is a popular HTTP client available as a JavaScript library with more than 22 million weekly downloads as of May 2022.

We can make API calls with Axios from JavaScript applications irrespective of whether the JavaScript is running on the front-end like a browser or the server-side.

In this article, we will understand Axios and use its capabilities to make different types of REST API calls from JavaScript applications.

Example Code

This article is accompanied by a working code example on GitHub.

Why do we need Axios

Let us first understand why do we need to use a library like Axios. JavaScript already provides built-in objects: XMLHttpRequest and the Fetch API for interacting with APIs.

Axios in contrast to these built-in objects is an open-source library that we need to include in our application for making API calls over HTTP. It is similar to the Fetch API and returns a JavaScript Promise object but also includes many powerful features.

One of the important capabilities of Axios is its isomorphic nature which means it can run in the browser as well as in server-side Node.js applications with the same codebase.

Axios is also a promise-based HTTP client that can be used in plain JavaScript as well as in advanced JavaScript frameworks like React, Vue.js, and Angular.

It supports all modern browsers, including support for IE 8 and higher.

In the following sections, we will look at examples of using these features of Axios in our applications.

Installing Axios and Other Prerequisites For the Examples

We have created the following applications to simulate APIs on the server consumed by other applications on the server and the browser with REST APIs :

Applications

  1. apiserver: This is a Node.js application written using the Express Framework that will contain the REST APIs.
  2. serversideapps: This is also a Node.js written in Express that will call the REST APIs exposed by the apiserver application using the Axios HTTP client.
  3. reactapp: This is a front-end application written in React which will also call the REST APIs exposed by the apiserver application.

Instead of Express, we could have used any other JavaScript framework or even raw JavaScript applications. To understand Express, please refer to our Express series of articles starting with Getting started on Express.

We will need to install the Axios library in two of these applications: serversideapps and reactapp which will be making API calls. Let us change to these directories one by one and install Axios using npm:

The package.json in our Node.js express application after installing the axios module looks like this:

{
  "name": "serversideapp",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  ...
  ...
  "dependencies": {
    "axios": "^0.27.2",
    "cors": "^2.8.5",
    "express": "^4.18.1"
  }
}

We can see the axios module added as a dependency in the dependencies element.

If we want to call APIs with Axios from a vanilla JavaScript application, then we need to include it from a Content delivery network (CDN) as shown here:

<script src="https://unpkg.com/axios/dist/axios.min.js"></script>

After setting up our applications, let us now get down to invoking the APIs exposed by the apiserver from the serversideapp and the reactapp using the Axios HTTP client in the following sections.

Sending Requests with the Axios Instance

Let us start by invoking a GET method with the Axios HTTP client from our server-side application: serversideapp.

For doing this, we will add an Express route handler function with a URL: /products to the application. In the route handler function, we will fetch the list of products by calling an API from our apiserver with the URL: http://localhost:3002/products.

We will use the signature: axios(config) on the default instance provided by the Axios HTTP client for doing this:

const express = require('express')

// Get the default instance
const axios = require('axios')

const app = express()

// Express route handler with URL: '/products' and a handler function
app.get('/products', (request, response) => {

  // Make the GET call by passing a config object to the instance
  axios({
    method: 'get',
    url: 'http://localhost:3002/products'
  }).then(apiResponse => {
     // process the response
     const products = apiResponse.data
     response.json(products)
  })
  
})

In this example, we are first calling require('axios') for getting an instance: axios set up with a default configuration.

Then we are passing a configuration argument to the axios instance containing the method parameter set to the HTTP method: get and the url parameter set to the URL of the REST endpoint: http://localhost:3002/products. The url parameter is mandatory while we can omit the method parameter that will then default to get.

This method returns a JavaScript Promise object which means the program does not wait for the method to complete before trying to execute the subsequent statement. The Promise is either fulfilled or rejected, depending on the response from the API.

We use the then() method as in this example for processing the result. The then() method gets executed when the Promise is fulfilled . In our example, in the then method, we are extracting the list of products by calling apiResponse.data.

Similarly, a POST request for adding a new product made with the axios default instance will look like this:

const express = require('express')

// Get the default instance
const axios = require('axios')

const app = express()

// Express route handler with URL: '/products/new' and a handler function
app.post('/products/new', async (request, response) => {

  const name = request.body.name
  const brand = request.body.brand

  const newProduct = {name: name, brand:brand}

  // Make the POST call by passing a config object to the instance
  axios({
    method: 'post',
    url: 'http://localhost:3002/products',
    data: newProduct,
    headers: {'Authorization': 'XXXXXX'}
  }).then(apiResponse=>{
     const products = apiResponse.data
     response.json(products)
  })
})

In this example, in addition to what we did for calling the GET method, we have set the data element containing the JSON representation of the new Product along with an Authorization header. We are processing the response in the then function on the Promise response where we are extracting the API response data by calling apiResponse.data.

For more involved processing of the API response, it will be worthwhile to understand all the elements of the response returned by the API call made with axios :

  • data: Response payload sent by the server
  • status: HTTP status code from the server response
  • statusText: HTTP status message from the server response
  • headers: HTTP headers received in the API response
  • config: config sent to the axios instance for sending the request
  • request: Request that generated this response. It is the last ClientRequest instance in node.js (in redirects) and an XMLHttpRequest instance in the browser.

Sending Requests with the Convenience Instance Methods of Axios

Axios also provides an alternate signature for making the API calls by providing convenience methods for all the HTTP methods like:axios.get(), axios.post(), axios.put(), axios.delete(), etc.

We can write the previous example for calling the GET method of the REST API using the convenience method: axios.get() as shown below:

const express = require('express')

// Get the default instance
const axios = require('axios')

const app = express()

// Express route handler for making a request for fetching a product
app.get('/products/:productName', (request, response) => {

  const productName = request.params.productName  

  axios.get(`http://localhost:3002/products/${productName}`)
  .then(apiResponse => {
     const product = apiResponse.data
     response.json(product)
  })   
})

In this example, in the Express route handler function, we are calling the get() method on the default instance of axios and passing the URL of the REST API endpoint as the sole argument. This code looks much more concise than the signature: axios(config) used in the example in the previous section.

The signature: axios.get() is always preferable for calling the REST APIs due to its cleaner syntax. However, the signature: axios(config) of passing a config object containing the HTTP method, and URL parameters to the axios instance can be used in situations where we want to construct the API calls dynamically.

The get() method returns a JavaScript Promise object similar to our earlier examples, where we extract the list of products inside the then function.

Instead of appending the request query parameter in the URL in the previous example, we could have passed the request parameter in a separate method argument: params as shown below:

const axios = require('axios')
axios.get(`http://localhost:3002/products/`, {
    params: {
      productName: productName
    }
  })
  .then(apiResponse => {
     const product = apiResponse.data
     response.json(product)
  })   

We could have also used the async/await syntax to call the get() method:

const express = require('express')
const axios = require('axios')

const app = express()

app.get('/products/async/:productName', async (request, response) => {
  const productName = request.params.productName  

  const apiResponse = await axios.get(`http://localhost:3002/products/`, {
    params: {
      productName: productName
    }
  })

  const product = apiResponse.data
  response.json(product)
})

async/await is part of ECMAScript 2017 and is not supported in older browsers like IE.

Let us next make a POST request to an API with the convenience method axios.post():

const express = require('express')
const axios = require('axios')

const app = express()

app.post('/products', async (request, response) => {

    const name = request.body.name
    const brand = request.body.brand

    const newProduct = {name: name, brand:brand}

    const apiResponse = await axios.post(`http://localhost:3002/products/`, newProduct)

    const product = apiResponse.data
    response.json({result:"OK"})
})

Here we are using the async/await syntax to make a POST request with the axios.post() method. We are passing the new product to be created as a JSON as the second parameter of the post() method.

Using Axios in Front-End Applications

Let us look at an example of using Axios in a front-end application built with the React library. The below snippet is from a React component that calls the API for fetching products:

import React, { useState } from 'react'
import axios from 'axios'

export default function ProductList(){
    const [products, setProducts] =  useState([])

    const fetchProducts = ()=>{
       axios.get(`http://localhost:3001/products`)
      .then(response => {
        const products = response.data
        setProducts(products)
      })      
     
    }

    return (
        <>
          <p>Product List</p>
          <p><button onClick={fetchProducts}>Fetch Products</button></p>
          <ul>
          {
            products
              .map(product =>
                <li key={product.id}>{product.name}&nbsp;{product.brand}</li>
              )
          }
          </ul>
        </>
    )
}

As we can see, the code for making the API call with Axios is the same as what we used in the Node.js application in the earlier sections.

Sending Multiple Concurrent Requests with Axios

In many situations, we need to combine the results from multiple APIs to get a consolidated result. With the Axios HTTP client, we can make concurrent requests to multiple APIs as shown in this example:

const express = require('express')

// get the default axios instance
const axios = require('axios')

const app = express()

// Route Handler
app.get('/products/:productName/inventory', (request, response) => {

  const productName = request.params.productName

  // Call the first API for product details
  const productApiResponse = axios
            .get(`http://localhost:3002/products/${productName}`)

  // Call the second API for inventory details
  const inventoryApiResponse = axios
            .get(`http://localhost:3002/products/${productName}/itemsInStock`)

  // Consolidate results into a single result
  Promise.all([productApiResponse, inventoryApiResponse])
  .then(results=>{
      const productData = results[0].data
      const inventoryData = results[1].data
      let aggregateData = productData
      aggregateData.unitsInStock = inventoryData.unitsInStock
      response.send(aggregateData)
    
  })
})

In this example, we are making requests to two APIs using the Promise.all() method. We pass an iterable of the two Promise objects returned by the two APIs as input to the method.

In response, we get a single Promise object that resolves to an array of the results of the input Promise objects.

This Promise object returned as the response will resolve only when all of the input promises are resolved, or if the input iterable contains no promises.

Overriding the default Instance of Axios

In all the examples we have seen so far, we used the require('axios') to get an instance of axios which is configured with default parameters. If we want to add a custom configuration like a timeout of 2 seconds, we need to use Axios.create() where we can pass the custom configuration as an argument.

An Axios instance created with Axios.create() with a custom config helps us to reuse the provided configuration for all the API invocations made by that particular instance.

Here is an example of an axios instance created with Axios.create() and used to make a GET request:

const express = require('express')
const axios = require('axios')

const app = express()

// Express Route Handler
app.get('/products/deals', (request, response) => {
  
  // Create a new instance of axios
  const new_instance = axios.create({
    baseURL: 'http://localhost:3002/products',
    timeout: 1000,
    headers: {
      'Accept': 'application/json',
      'Authorization': 'XXXXXX'
    }
  })

  new_instance({
    method: 'get',
    url: '/deals'
  }).then(apiResponse => {
     const products = apiResponse.data
     response.json(products)
  })
  
})

In this example, we are using axios.create() to create a new instance of Axios with a custom configuration that has a base URL of http://localhost:3002/products and a timeout of 1000 milliseconds. The configuration also has an Accept and Authorization headers set depending on the API being invoked.

The timeout configuration specifies the number of milliseconds before the request times out. If the request takes longer than the timeout interval, the request will be aborted.

Intercepting Requests and Responses

We can intercept requests or responses of API calls made with Axios by setting up interceptor functions. Interceptor functions are of two types:

  • Request interceptor for intercepting requests before the request is sent to the server.
  • Response interceptor for intercepting responses received from the server.

Here is an example of an axios instance configured with a request interceptor for capturing the start time and a response interceptor for computing the time taken to process the request:

const express = require('express')
const axios = require('axios')

const app = express()

// Request interceptor for capturing start time
axios.interceptors.request.use(
   (request) => {
    request.time = { startTime: new Date() }
    return request
  },
  (err) => {
    return Promise.reject(err)
  }
)

// Response interceptor for computing duration
axios.interceptors.response.use(
   (response) => {
    response.config.time.endTime = new Date()
    response.duration =
           response.config.time.endTime - response.config.time.startTime
    return response
  },
  (err) => {
    return Promise.reject(err);
  }
)

// Express route handler
app.get('/products', (request, response) => {

  axios({
    method: 'get',
    url: 'http://localhost:3002/products'
  }).then(apiResponse=>{
     const products = apiResponse.data

     // Print duration computed in the response interceptor
     console.log(`duration ${apiResponse.duration}` )
     response.json(products)
  })
  
})

In this example, we are setting the request.time to the current time in the request interceptor. In the response interceptor, we are capturing the current time in response.config.time.endTime and computing the duration by deducting from the current time, the start time captured in the request interceptor.

Interceptors are a powerful feature that can be put to use in many use cases where we need to perform actions common to all API calls. In the absence of interceptors, we will need to repeat these actions in every API call. Some of these examples are:

  1. Verify whether the access token for making the API call has expired in the request interceptor. If the token has expired, fetch a new token with the refresh token.
  2. Attach specific headers required by the API to the request in the request interceptor. For example, add the Authorization header to every API call.
  3. Check for HTTP status, headers, and specific fields in the response to detect error conditions and trigger error handling logic.

Handling Errors in Axios

The response received from Axios is a JavaScript promise which has a then() function for promise chaining, and a catch() function for handling errors. So for handling errors, we should add a catch() function at the end of one or more then() functions as shown in this example:

const express = require('express')
const axios = require('axios')

const app = express()

// Express route handler
app.post('/products/new', async (request, response) => {

  const name = request.body.name
  const brand = request.body.brand

  const newProduct = {name: name, brand: brand}

  axios({
    method: 'post',
    url: 'http://localhost:3002/products',
    data: newProduct,
    headers: {'Authorization': 'XXXXXX'}
  }).then(apiResponse=>{
     const products = apiResponse.data
     response.json(products)
  }).catch(error => {
    if (error.response) {
        console.log("response error")
    } else if (error.request) {
        console.log("request error")
    } else {
      console.log('Error', error.message);
    }
    response.send(error.toJSON())
  })
})

In this example, we have put the error handling logic in the catch() function. The callback function in the catch() takes the error object as input. We come to know about the source of the error by checking for the presence of the response property and request property in the error object with error.response and error.request.

An error object with a response property indicates that our server returned a 4xx/5xx error and accordingly return a helpful error message in the response.

In contrast, An error object with a request property indicates network errors, a non-responsive backend, or errors caused by unauthorized or cross-domain requests.

The error object may not have either a response or request object attached to it. This indicates errors related to setting up the request, which eventually triggered the error. An example of this condition is an URL parameter getting omitted while sending the request.

Cancelling Initiated Requests

We can also cancel or abort a request when we no longer require the requested data for example, when the user navigates from the current page to another page. To cancel a request, we use the AbortController class as shown in this code snippet from our React application:

import React, { useState } from 'react'
import axios from 'axios'

export default function ProductList(){
    const [products, setProducts] =  useState([])

    const controller = new AbortController()

    const abortSignal = controller.signal
    const fetchProducts = ()=>{
       axios.get(`http://localhost:3001/products`, {signal: abortSignal})
      .then(response => {
        const products = response.data
        setProducts(products)
      })      
      controller.abort()
    }

    return (
        <>
          ...
          ...
        </>
    )
}

As we can see in this example, we are first creating a controller object using the AbortController() constructor, then storing a reference to its associated AbortSignal object using the signal property of the AbortController.

When the axios request is initiated, we pass in the AbortSignal as an option inside the request’s options object: {signal: abortSignal}. This associates the signal and controller with the axios request and allows us to abort the request by calling the abort() method on the controller.

Using Axios in TypeScript

Let us now see an example of using Axios in applications authored in TypeScript.

We will first create a separate folder: serversideapp_ts and create a project in Node.js by running the below command by changing into this directory:

cd serversideapp_ts
npm init -y

Let us next add support for TypeScript to our Node.js project by performing the following steps:

  1. Installing Typescript and ts-node with npm:
npm i -D typescript ts-node
  1. Creating a JSON file named tsconfig.json with the below contents in our project’s root folder to specify different options for compiling the TypeScript code:
{
  "compilerOptions": {
    "module": "commonjs",
    "target": "es6",
    "rootDir": "./",
    "esModuleInterop": true
  }
}
  1. Installing the axios module with npm:

Axios includes TypeScript definitions, so we do not have to install them separately.

After enabling the project for TypeScript, let us add a file index.ts which will contain our code for making API calls with Axios in TypeScript.

Next, we will make an HTTP GET request to our API for fetching products as shown in this code snippet:

import axios from 'axios';

type Product = {
  id: number;
  email: string;
  first_name: string;
};

type GetProductsResponse = {
  data: Product[];
};

async function getProducts() {
  try {
    // 👇️ fetch products from an API
    const { data, status } = await axios.get<GetProductsResponse>(
      'http://localhost:3002/products',
      {
        headers: {
          Accept: 'application/json',
        },
      },
    );

    console.log(JSON.stringify(data, null, 4));

    console.log(`response status is: ${status}`);

    return data;
  } catch (error) {
    if (axios.isAxiosError(error)) {
      console.log(`error message: ${error.message}`);
      return error.message;
    } else {
      console.log(`unexpected error: ${error}`);
      return 'An unexpected error occurred';
    }
  }
}

getProducts();

In this example, we are getting the axios instance by importing it from the module in the first line. We have next defined two type definitions: Product and GetProductsResponse.

After that we have defined a method getProducts() where we are invoking the API with the axios.get() method using the async/await syntax. We are passing the URL of the API endpoint and an Accept header to the axios.get() method.

Let us now run this program with the below command:

We are calling the method: getProducts() in the last line in the program, which prints the response from the API in the terminal/console.

Conclusion

In this article, we looked at the different capabilities of Axios. Here is a summary of the important points from the article:

  1. Axios is an HTTP client for calling REST APIs from JavaScript programs running in the server as well as in web browsers.
  2. We create default instance of axios by calling require('axios')
  3. We can override the default instance of axios with the create() method of axios to create a new instance, where we can override the default configuration properties like ‘timeout’.
  4. Axios allows us to attach request and response interceptors to the axios instance where we can perform actions common to multiple APIs.
  5. Error conditions are handled in the catch() function of the Promise response.
  6. We can cancel requests by calling the abort() method of the AbortController class.
  7. The Axios library includes TypeScript definitions, so we do not have to install them separately when using Axios in TypeScript applications.

You can refer to all the source code used in the article
on Github.

Обработка ошибок

axios.get('/user/12345')
  .catch(function (error) {
    if (error.response) {
      // Запрос был сделан, и сервер ответил кодом состояния, который
      // выходит за пределы 2xx
      console.log(error.response.data);
      console.log(error.response.status);
      console.log(error.response.headers);
    } else if (error.request) {
      // Запрос был сделан, но ответ не получен
      // `error.request`- это экземпляр XMLHttpRequest в браузере и экземпляр
      // http.ClientRequest в node.js
      console.log(error.request);
    } else {
      // Произошло что-то при настройке запроса, вызвавшее ошибку
      console.log('Error', error.message);
    }
    console.log(error.config);
  });

Используя параметр конфигурации validateStatus, вы можете определить HTTP-коды, которые должны вызывать ошибку.

axios.get('/user/12345', {
  validateStatus: function (status) {
    return status < 500; // Разрешить, если код состояния меньше 500
  }
})

Используя toJSON, вы получаете объект с дополнительной информацией об ошибке HTTP.

axios.get('/user/12345')
  .catch(function (error) {
    console.log(error.toJSON());
  });

  1. Use Axios in TypeScript
  2. Types in the Axios Library
  3. Use Axios to Make REST API Calls in TypeScript
  4. Create an Axios Config File in TypeScript

Axios Response in TypeScript

Axios is a prevalent JavaScript library for managing making requests to a backend resource. With the growing demand for TypeScript, types have been added to the Axios library.

This tutorial will use Axios to make REST API calls in TypeScript.

Use Axios in TypeScript

The first step is to install Axios in a project. Axios can be installed in a NodeJs or React project.

npm install axios

// or

yarn install axios

Now, Axios can be used in the project with other packages.

Types in the Axios Library

Several pre-built types are available in the Axios Github repository. This tutorial will focus on some of the important types in Axios.

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

The AxiosResponse is the response object returned as a Promise due to a REST API call such as GET or POST.

get<T = never, R = AxiosResponse<T>>(url: string, config?: AxiosRequestConfig<T>): Promise<R>;

post<T = never, R = AxiosResponse<T>>(url: string, data?: T, config?: AxiosRequestConfig<T>): Promise<R>;

The above code shows two REST API methods in Axios and their types. The AxiosRequestConfig is a pretty big interface defined here.

Some of the important fields in the interface are:

export interface AxiosRequestConfig<T = any> {
    url?: string;
    method?: Method;
    baseURL?: string;
    data?: T;
    headers?: Record<string, string>;
    params?: any;
    ...
}

Thus, one can either put the baseURL or the full URL as part of the request. One of the critical things to notice here is the data field in AxiosRequestConfig and AxiosResponse, which are generic types T and can accept any type.

Use Axios to Make REST API Calls in TypeScript

The above types can make typed REST API calls in TypeScript. Suppose an e-commerce website makes a REST API call to its backend to show all the books available on its frontend.

interface Book {
    isbn : string;
    name : string;
    price : number;
    author : string;
}

axios.get<Book[]>('/books', {
    baseURL : 'https://ecom-backend-example/api/v1',
}).then( response => {
    console.log(response.data);
    console.log(response.status);
})

Using React, one can even store the returned data as part of the AxiosResponse in a state with something like:

const [books, setBooks] = React.useState<Book[]>([]);
axios.get<Book[]>('/books', {
    baseURL : 'https://ecom-backend-example/api/v1',
}).then( response => {
    setBooks(response.data);
})

Create an Axios Config File in TypeScript

Axios provides many useful types and can be used to create a config file which we can further use to make REST API calls throughout the application.


interface Book {
    isbn : string;
    name : string;
    price : number;
    author : string;
}

const instance = axios.create({
    baseURL: 'https://ecom-backend-example/api/v1',
    timeout: 15000,
});

const responseBody = (response: AxiosResponse) => response.data;

const bookRequests = {
    get: (url: string) => instance.get<Book>(url).then(responseBody),
    post: (url: string, body: Book) => instance.post<Book>(url, body).then(responseBody),
    delete: (url: string) => instance.delete<Book>(url).then(responseBody),
};

export const Books {
    getBooks : () : Promise<Book[]> => bookRequests.get('/books'),
    getSingleBook : (isbn : string) : Promise<Book> => bookRequests.get(`/books/${isbn}`),
    addBook : (book : Book) : Promise<Book> => bookRequests.post(`/books`, book),
    deleteBook : (isbn : string) : Promise<Book> => bookRequests.delete(`/books/${isbn}`)
}

Thus, throughout the application, the config can be used as:

import Books from './api' // config added in api.ts file
const [books, setBooks] = React.useState<Book[]>([]);
Books.getPosts()
    .then((data) => {
        setBooks(data);
    })
    .catch((err) => {
        console.log(err);
    });

Thus, Axios enables us to make clean and strongly typed implementations of REST API calls.

Понравилась статья? Поделить с друзьями:
  • Axios error message
  • Axios error get body
  • Axios error config
  • Axios create error
  • Axios catch error typescript