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 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
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
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 AlhassanObaida Alhassan
4861 gold badge5 silver badges11 bronze badges
2
Содержание
- Axios & Error handling like a boss 😎
- Introduction
- The Problem
- The very ugly solution
- The elegant and recommended solution
- The IMPROVED and elegant solution
- Customize for one http call
- The Final Thoughts
- Leaning into TypeScript for type-safe error handling
- 2020-04-12
- Why throwing errors isn’t type-safe
- Using a Result type to leverage the type system
- 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
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 :
apiserver
: This is a Node.js application written using the Express Framework that will contain the REST APIs.serversideapps
: This is also a Node.js written in Express that will call the REST APIs exposed by theapiserver
application using theAxios
HTTP client.reactapp
: This is a front-end application written in React which will also call the REST APIs exposed by theapiserver
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} {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:
- 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.
- Attach specific headers required by the API to the request in the request interceptor. For example, add the
Authorization
header to every API call. - 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:
- Installing Typescript and
ts-node
withnpm
:
npm i -D typescript ts-node
- 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
}
}
- Installing the
axios
module withnpm
:
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:
- Axios is an HTTP client for calling REST APIs from JavaScript programs running in the server as well as in web browsers.
- We create default instance of
axios
by callingrequire('axios')
- We can override the default instance of
axios
with thecreate()
method ofaxios
to create a new instance, where we can override the default configuration properties like ‘timeout’. - Axios allows us to attach request and response interceptors to the
axios
instance where we can perform actions common to multiple APIs. - Error conditions are handled in the
catch()
function of thePromise
response. - We can cancel requests by calling the
abort()
method of theAbortController
class. - 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.
- Use Axios in TypeScript
- Types in the Axios Library
- Use Axios to Make REST API Calls in TypeScript
- Create an Axios
Config
File 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.