Axios error type typescript

I have the following working code: try { self.loadingFrameMarkup = true; const { data }: AxiosResponse = yield axios....

I have the following working code:

                try {
                    self.loadingFrameMarkup = true;
                    const { data }: AxiosResponse<IMarkupFrameData> = yield axios.post<IMarkupFrameData>(
                        Endpoints.LoadMarkupFrame,
                        {
                            studyID: self.studyID,
                            frameIndex: frameIndex,
                        }
                    );

                    result = {
                        ...data,
                        layers: data.layers.filter((it) => it),
                    };
                    self.loadingFrameMarkup = false;
                } catch (error: AxiosError | Error) {
                    if (error?.response?.status === 401) {
                        self.state = QueueState.Unauthorized;
                    } else {
                        self.state = QueueState.Failure;
                    }
                }

typescript-eslint show error
Catch clause variable type annotation must be ‘any’ or ‘unknown’ if specified.ts(1196)
(alias) interface AxiosError<T = unknown, D = any>

What type should the error be in Axios?

asked Oct 17, 2021 at 10:23

Alexei Delezhov's user avatar

Alexei DelezhovAlexei Delezhov

1,1832 gold badges9 silver badges13 bronze badges

You can’t directly give type definition of error in the catch block. You can read more about this here.
Best solution would be giving type inside the catch block, as :

catch (err) {
       if ((error as AxiosError)?.response?.status === 401) {
             self.state = QueueState.Unauthorized;
           } else {
              self.state = QueueState.Failure;
         }
     }

answered Oct 17, 2021 at 10:44

NeERAJ TK's user avatar

You can do something similar to

    try {
        ...
        ...
        ...
    } catch (err) {
      console.log(err as AxiosError)
      // or
      // const error = err as AxiosError
    }

answered Oct 17, 2021 at 10:38

MRsabs's user avatar

0

The types in TypeScript only exist until the code is compiled. Once it’s compiled, all you’ve got is typeless JavaScript.

Having type filters on catch clauses would require checking the errors’ types at runtime, and there’s simply no reliable way to do that, so simply assign it to any or unknown and then cast the error if applicable

catch (error: Error| any){
/// code
}

answered Oct 17, 2021 at 10:43

Obaida Alhassan's user avatar

Obaida AlhassanObaida Alhassan

4861 gold badge5 silver badges11 bronze badges

2

Содержание

  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. Leaning into TypeScript for type-safe error handling
  10. 2020-04-12
  11. Why throwing errors isn’t type-safe
  12. Using a Result type to leverage the type system
  13. Influences and Prior Art

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!

Источник

Leaning into TypeScript for type-safe error handling

2020-04-12

Over the past year I’ve been working on a large TypeScript project, and I’m happy to say I’m now officially on «team TypeScript».

Previously I’ve felt a little cautious about jumping on the TypeScript hype train for two main reasons:

«It’s too verbose.» Compared to JavaScript, TypeScript is certainly much more verbose. Other statically typed languages, like Reason and Elm don’t require you to write nearly as many type annotations either.

«The type system isn’t strong enough.» Using the any type lets you break out of type-checking entirely, so the type system is only strong as long as you’re diligent enough to add the type annotations correctly. Compare this to Reason and Elm—the other statically-typed languages I’ve worked in—and you’ll see that they both have much stronger type systems.

I’ve written about both of these before—here’s an article I wrote a while back on why Reason is so cool. I still think Reason is cool, but I’m no longer hesitant about TypeScript.

After working with TypeScript (TS) for a while, I think it has a type system that’s strong enough to provide confidence. The verbosity I felt when using TS was more due to unfamiliarity.

I’m a firm believer that when using a static type system you get more out of it by leaning into the type system rather than fighting against the type system. It’s best if you think of the compiler as your friend instead of your enemy.

Which brings me to the focus of this article—how we can work with TypeScript to handle errors.

Why throwing errors isn’t type-safe

A very common style of handling errors in JavaScript (and TypeScript) is to throw an error. throw is built-in to JavaScript—by throw ing an Error it propagates up the stack until it reaches a catch block.

Using throw to handle errors isn’t bad—many server frameworks like NestJS and Express use this method since you can use a single catch block at the top of your app. This means you’re able to handle all errors in your app in a single location—regardless of their point of origin.

In React you can catch errors coming from your components using componentDidCatch and creating an ErrorBoundary component (I’m not too sure about other front-end frameworks since I mostly work in React).

However, just because throw is common doesn’t mean it’s type-safe. Anytime you throw an error, you need to have a catch block to catch it or it could potentially bubble up and crash your app! TypeScript isn’t able to infer that your code using throw is nested inside a catch block so whenever you use catch you’re relying on developer diligence instead of the type system to make sure your app won’t crash on flaky data.

Using a Result type to leverage the type system

So, how can we manage failure and uncertainty in our app in a way that allows the type system to catch potentially unsafe code for us?

I have a custom type that I use to wrap unsafe code, here’s what it looks like:

The Result type is an example of a TypeScript union type—it represents something that could be a ResultSuccess or a ResultError . ResultSuccess and ResultError both have a type property, but other than that the objects are completely different.

Here’s what it would look like to have a function that returns a Result type.

This example is fairly small—the function doesn’t do much more than checking whether the value is 0 or not. However, the benefits of a Result type become clearer when we slap it on an API request:

One note about using axios for data fetching is that it will automatically throw an error if the API returns anything with a 400-level or 500-level response. While this means you don’t have to check for successful responses, this opens us up to the danger that an uncaught exception crashes the entire app.

However, if we wrap the axios call in a try/catch block, how do we type that? And what if we actually do want to throw an error from our failed requests?

When we have our «risky» code return a Result , TypeScript is smart enough to force us to check the Result type for a successful response before we try to use the value.

I’ll admit, having these types of checks around the codebase isn’t the «prettiest» thing in the world. But it’s certainly better than having the app randomly crash when data is missing or API requests fail. Fault-tolerance and safety are more valuable than avoiding a couple if blocks!

Using Result allows us to take a potential runtime error and turn it into a compile-type error:

As an added bonus, if you do want to throw an error, the ResultError type contains an actual Error . This gives you full control over how and when errors get thrown from your app.

And that’s it! I’ve been using this approach for most of the API calls coming into the front-end. I’m really happy with how it’s turned out on the apps where I’ve implemented it! Instead of having random crashes, I’m forced time and time again by the compiler to either design error states for missing data or API failures.

Influences and Prior Art

This pattern isn’t something I came up with out of the blue, so I want to highlight a couple key influences I had in discovering this pattern.

One of the key influences for this Result interface came from the Option type in Reason. The Option type is the way that Reason allows you to deal with nullable data without compromising its type safety. It’s fairly similar to the Maybe monad in Elm and Haskell if you’re a fan of those languages.

Secondly, I’ve gotten a lot of great TypeScript patterns from TypeScript Deep Dive by Basarat Ali Syed. Specifically, his section on exception handling was influential in me using this Result pattern.

Lastly, I wanted to reiterate that that the best solutions with TypeScript typically come from leaning into the type system rather than fighting against it. This Result type came out of desiring to find a way to force my team (myself included) check for error states rather than optimistically trusting that certain functions (primarily API requests) will always return successfully.

If you enjoyed this, please let me know on my Twitter or LinkedIn with a share! If you found any errors or bugs in this article let me know or submit a pull request to update this article.

Источник

Kent Dodds wrote an awesome article on how he handles catch-block error messages in Typescript.

This works great but I encountered some issues with my MSW and Axios setup. Specifically, I was unable to access the error.response.data.message property and display the text of that as my error message. Typescript would spit out the error property response does not exist on type Error.

Seems this is because of how Axios handles errors:

axios.get('/user/12345')
.catch(function (error) {
if (error.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
} else if (error.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
console.log(error.request);
} else {
// Something happened in setting up the request that triggered an Error
console.log('Error', error.message);
}
console.log(error.config);
});

As you can see error has response and request keys. Axios does actually have Typescript types available so we can use those in our type guards to get around the issue. This is my solution based on this:

try {
const response = await loginService(credentials);
setUser(response);
} catch (error) {
let message;
if (axios.isAxiosError(error) && error.response) {
message = error.response.data.message;
} else message = String(error);
setErrorMessage(message);
}

Footer


Related


References

  • https://github.com/axios/axios/issues/3612

Md Mostafizur Rahman

Let’s imagine we are calling an API with axios within a try...catch block to get a list of posts. If the request is successful then we will get the response data, else we will get the error on our catch block

const fetchPosts = async () => {
  try {
    const response = await axios.get(
      "<https://jsonplaceholder.typicode.com/posts>"
    );
    // Do something with the response
  } catch (error) {
    // Need to handle this error
  }
};

Enter fullscreen mode

Exit fullscreen mode

But the problem is typescript assumes this error object as unknown type. So, typescript won’t allow us to access any properties inside this error object.
To handle this situation, we can assert the error object type like this

import axios, {AxiosError} from "axios";

const fetchPosts = async () => {
  try {
    const response = await axios.get(
      "<https://jsonplaceholder.typicode.com/posts>"
    );
    // Do something with the response
  } catch (e) {
    const error = e as AxiosError;
    // Need to handle this error
  }
};

Enter fullscreen mode

Exit fullscreen mode

But the assertion is not a good practice and we should avoid doing this. By asserting type you are forcing the typescript to stop doing his work. So this may be problematic sometimes.

A Good Solution

We can use typeguard to handle this kind of situation and axios also provides a typeguard for handling errors. Here how it looks like

const fetchPosts = async () => {
  try {
    const response = await axios.get(
      "<https://jsonplaceholder.typicode.com/posts>"
    );
    // Do something with the response
  } catch (error) {
    if (axios.isAxiosError(error)) {
      console.log(error.status)
      console.error(error.response);
      // Do something with this error...
    } else {
      console.error(error);
    }
  }
};

Enter fullscreen mode

Exit fullscreen mode

We can also pass generic to type the error response and request object with this typeguard.

interface ValidationError {
  message: string;
  errors: Record<string, string[]>
}

const fetchPosts = async () => {
  try {
    const response = await axios.get(
      "<https://jsonplaceholder.typicode.com/posts>"
    );
    // Do something with the response
  } catch (error) {
    if (axios.isAxiosError<ValidationError, Record<string, unknown>>(error)) {
      console.log(error.status)
      console.error(error.response);
      // Do something with this error...
    } else {
      console.error(error);
    }
  }
};

Enter fullscreen mode

Exit fullscreen mode

N:B: You can also use this in a .catch() method.

To learn about more tips and tricks with typescript, follow me on LinkedIn

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.

  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