New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and
privacy statement. We’ll occasionally send you account related emails.
Already on GitHub?
Sign in
to your account
Closed
EkeMinusYou opened this issue
Jan 30, 2021
· 13 comments
Comments
Describe the issue
I have question how type axios error in Typescript.
I know AxiosError
type is exposed, but I don’t think it should be used because it’s unclear if it’s an Axios Error when caught.
Example Code
import axios, {AxiosError} from 'axios'; axios.get('v1/users') .catch((e: AxiosError) => { // really AxiosError? console.log(e.message); }
Should I do something judgement before typing with AxiosError?
Please tell me a good practice.
Expected behavior, if applicable
A clear and concise description of what you expected to happen.
Environment
- Axios Version [e.g. 0.18.0]
- Adapter [e.g. XHR/HTTP]
- Browser [e.g. Chrome, Safari]
- Browser Version [e.g. 22]
- Node.js Version [e.g. 13.0.1]
- OS: [e.g. iOS 12.1.0, OSX 10.13.4]
- Additional Library Versions [e.g. React 16.7, React Native 0.58.0]
Additional context/Screenshots
Add any other context about the problem here. If applicable, add screenshots to help explain.
quandinhphan reacted with thumbs down emoji
@EkeMinusYou This is a great question. If you look at the types you’ll see that AxiosError
has a property isAxiosError
that is used to detect types, when combined with the builtin typeguard:
.catch((err: Error | AxiosError) {
if (axios.isAxiosError(error)) {
// Access to config, request, and response
} else {
// Just a stock error
}
})
This is a newer feature, so I suggest you update to the newest version of Axios to ensure it is present.
luistak, budasnorbi, slava-arapov, demostheneslld, iamrobins, muka, dikshit-leora, Luis-GiraldoM, lucasousi, martinmcneela, and 17 more reacted with hooray emoji
RomanKornev, lucasousi, martinmcneela, dngwoodo, euclidesdry, libeyondea, sou-gabriel, MinskLeo, bsor-dev, dovranJorayev, and dagorod reacted with confused emoji
luistak, pablodicosta, pitfritsch, cuzzlor, filev-svt, RSchneider94, demostheneslld, alefduarte, iamrobins, hummli, and 39 more reacted with heart emoji
luistak, michaeloliverx, demostheneslld, iamrobins, svlipatnikov, dikshit-leora, AmitThakur11, jtreeves, Luis-GiraldoM, sametbalta, and 19 more reacted with rocket emoji
just in case this helps anyone else I created some middleware based on this answer to make the code a bit cleaner in the eventual request:
import axios, { AxiosError } from 'axios'; interface IErrorBase<T> { error: Error | AxiosError<T>; type: 'axios-error' | 'stock-error'; } interface IAxiosError<T> extends IErrorBase<T> { error: AxiosError<T>; type: 'axios-error'; } interface IStockError<T> extends IErrorBase<T> { error: Error; type: 'stock-error'; } export function axiosErrorHandler<T>( callback: (err: IAxiosError<T> | IStockError<T>) => void ) { return (error: Error | AxiosError<T>) => { if (axios.isAxiosError(error)) { callback({ error: error, type: 'axios-error' }); } else { callback({ error: error, type: 'stock-error' }); } }; }
Then the code looks like:
.catch( axiosErrorHandler<MyType>(res => { if (res.type === 'axios-error') { //type is available here const error = res.error; } else { //stock error } }) );
I have a scenario where the error response is different than the successful response and I’m trying to figure out what’s the best way to type it.
So far I came up with this but not sure it’s the best thing to do, I would appreciate your feedback 😄
https://codesandbox.io/s/upbeat-easley-ljgir?file=/src/index.ts
import request from "axios"; type TodoSuccessResponse = { userId: number; id: number; title: string; completed: boolean; }; type TodoErrorResponse = { error: string; }; async function main() { try { const res = await request.get<TodoSuccessResponse>( "https://jsonplaceholder.typicode.com/todos/1" ); console.log(res.data.id); } catch (err) { if (request.isAxiosError(err) && err.response) { // Is this the correct way? console.log((err.response?.data as TodoErrorResponse).error); } } }
It worked for me:
try { // statements } catch(err) { const errors = err as Error | AxiosError; if(!axios.isAxiosError(error)){ // do whatever you want with native error } // do what you want with your axios error }
Eitol and blazity-bmstefanski reacted with thumbs down emoji
I-am-abdulazeez and DhansAL reacted with hooray emoji
itslewisndungu, nik32, jvvoliveira, and trey-wallis reacted with heart emoji
Optionally use //@ts-ignore
for a easy pass.
raajnadar, PabloDinella, bauertill, elvizcacho, Marinell0, fariasmateuss, rametta, EsdrasXavier, Lawlzer, mikedelcastillo, and 134 more reacted with thumbs down emoji
danbower, igoramos77, dboscanv, CharleeWa, sledz-mobtex, oahmaro, rismailov, David-L-R, nik32, Almeida154, and 16 more reacted with laugh emoji
AquiGorka and bilalbutt044 reacted with hooray emoji
cnguyen-de, jmz7v, fariasmateuss, wozitto, oahmaro, ammaroker-r, Nelie-Taylor, SimonHausdorf, and patelnets reacted with confused emoji
AquiGorka, A-Baptiste, and bilalbutt044 reacted with heart emoji
AquiGorka reacted with rocket emoji
AquiGorka reacted with eyes emoji
axios.isAxiosError
is useful, but it doesn’t type-guard or allow you to specify a generic response type. I added a really simple util function in my codebase to help with those:
export function isAxiosError<ResponseType>(error: unknown): error is AxiosError<ResponseType> {
return axios.isAxiosError(error);
}
You can then use it to get fully-typed error response data:
type MyExpectedResponseType = {
thisIsANumber: number;
};
try {
// Make the axios fetch.
} catch (error: unknown) {
if (isAxiosError<MyExpectedResponseType>(error)) {
// "thisIsANumber" is properly typed here:
console.log(error.response?.data.thisIsANumber);
}
}
HMoen reacted with hooray emoji
askariacc, muhrizqiardi, 1majek, LautaroRiveiro, eboody, andrewvasilchuk, dominikalexanderlise, MattyMags, kingyong9169, MichalPniak, and 8 more reacted with heart emoji
axios.isAxiosError
is useful, but it doesn’t type-guard or allow you to specify a generic response type. I added a really simple util function in my codebase to help with those:export function isAxiosError<ResponseType>(error: unknown): error is AxiosError<ResponseType> { return axios.isAxiosError(error); }
You can then use it to get fully-typed error response data:
type MyExpectedResponseType = { thisIsANumber: number; }; try { // Make the axios fetch. } catch (error: unknown) { if (isAxiosError<MyExpectedResponseType>(error)) { // "thisIsANumber" is properly typed here: console.log(error.response?.data.thisIsANumber); } }
I am having a little problem understanding this.
MyExpectedResponseType is the expected data type to get back from a successful response?
Why do we want to access it in a failed response. I thought response.data would hold information on why it failed. For example data submitted to request was incorrect and it responds with which data field failed. That would be a different type to say the data type of a successful response
axios.isAxiosError
is useful, but it doesn’t type-guard or allow you to specify a generic response type. I added a really simple util function in my codebase to help with those:export function isAxiosError<ResponseType>(error: unknown): error is AxiosError<ResponseType> { return axios.isAxiosError(error); }
You can then use it to get fully-typed error response data:
type MyExpectedResponseType = { thisIsANumber: number; }; try { // Make the axios fetch. } catch (error: unknown) { if (isAxiosError<MyExpectedResponseType>(error)) { // "thisIsANumber" is properly typed here: console.log(error.response?.data.thisIsANumber); } }
I am having a little problem understanding this. MyExpectedResponseType is the expected data type to get back from a successful response? Why do we want to access it in a failed response. I thought response.data would hold information on why it failed. For example data submitted to request was incorrect and it responds with which data field failed. That would be a different type to say the data type of a successful response
I’d guess that this is useful in the case of known/expected types of Axios errors. For example, I use the following to provide type info to Laravel validation error responses (always status code 422
with message
& errors
data properties):
interface LaravelValidationResponse extends AxiosResponse { status: 422; data: { message: string; errors: Record<string, Array<string>>; }; } export interface LaravelValidationError extends AxiosError { response: LaravelValidationResponse; } function axiosResponseIsLaravelValidationResponse(response: AxiosResponse): response is LaravelValidationResponse { return response.status === 422 && typeof response.data?.message === 'string' && typeof response.data?.errors === 'object'; } export function isLaravelValidationError(error: unknown): error is LaravelValidationError { return Boolean( axios.isAxiosError(error) && error.response && axiosResponseIsLaravelValidationResponse(error.response) ); }
While I probably wouldn’t go with @btraut’s approach as it doesn’t allow for differentiating between different types of errors, it could be useful as a quick type cast.
@EkeMinusYou This is a great question. If you look at the types you’ll see that
AxiosError
has a propertyisAxiosError
that is used to detect types, when combined with the builtin typeguard:.catch((err: Error | AxiosError) { if (axios.isAxiosError(error)) { // Access to config, request, and response } else { // Just a stock error } })
This is a newer feature, so I suggest you update to the newest version of Axios to ensure it is present.
This is fantastic and very helpful. Thank you very much 🙏🏾
@EkeMinusYou This is a great question. If you look at the types you’ll see that
AxiosError
has a propertyisAxiosError
that is used to detect types, when combined with the builtin typeguard:.catch((err: Error | AxiosError) { if (axios.isAxiosError(error)) { // Access to config, request, and response } else { // Just a stock error } })
This is a newer feature, so I suggest you update to the newest version of Axios to ensure it is present.
Hi there @timemachine3030,
What is the Error
type? A type you created?
Error
is defined by the typescript libs for your target (in your tsconfig.json file). For example: https://github.com/microsoft/TypeScript/blob/main/lib/lib.es5.d.ts#L1052
Revisiting this after a year for learning more about ts/js I would rewrite it as:
.catch((err: unknown) { if (axios.isAxiosError(error)) { // Access to config, request, and response } else if (err instanceof Error) { // Just a stock error } else { // unknown } })
Assuming errors are of created by new Error(...)
in javascript is a bad practice. You can technically throw anything. throw "a string";
is valid, unfortunately.
The browser and node.js can throw native errors that don’t extend the Error
class. For example, GeolocationPositionError is not an Error
as it may not contain a stack trace.
my error interface.
interface Error { message: string[]; statusCode: number; }
my catch error.
try { } catch (err) { const error = err as AxiosError<Error>; console.log(error.response?.data.message); }
I got error of Catch clause variable type annotation must be 'any' or 'unknown' if specified.ts(1196)
with below code
import axios, { AxiosError } from "axios";
try {
} catch(error: AxiosError) {
throw Error(error);
}
How to throw axios error in TS?
asked Sep 21, 2021 at 6:46
2
Use AxiosError to cast error
import { AxiosError } from 'axios';
catch (error) {
const err = error as AxiosError
console.log(err.response?.data)
}
answered Dec 3, 2021 at 22:23
1
I would suggest to you, removing the error type like the following:
import axios from 'axios';
try {
// do what you want with axios
// axios.get('https://example.com/some-api');
} catch (error) {
// check if the error was thrown from axios
if (axios.isAxiosError(error)) {
// do something
// or just re-throw the error
throw error;
} else {
// do something else
// or creating a new error
throw new Error('different error than axios');
}
}
I just created a stackblitz for it.
And if you want to have a deeper look just have a look at this article
answered Sep 21, 2021 at 7:14
MaxMax
9861 gold badge12 silver badges28 bronze badges
7
You cannot write a specific annotation for the catch clause variable in typescript, this is because in javascript a catch clause will catch any exception that is thrown, not just exceptions of a specified type.
In typescript, if you want to catch just a specific type of exception, you have to catch whatever is thrown, check if it is the type of exception you want to handle, and if not, throw it again.
meaning: check if the error that is thrown is an axios error first, before doing anything.
try {
// do something
}catch (err) {
// check if error is an axios error
if (axios.isAxiosError(err)) {
// console.log(err.response?.data)
if (!err?.response) {
console.log("No Server Response");
} else if (err.response?.status === 400) {
console.log("Missing Username or Password");
} else if (err.response?.status === 401) {
console.log("Unauthorized");
} else {
console.log("Login Failed");
}
}
}
answered May 19, 2022 at 16:36
neilneil
1191 gold badge2 silver badges12 bronze badges
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.
When you are coming from languages like Java, C++, or C#, you are used to doing your error handling by throwing exceptions. And subsequently, catching them in a cascade of catch
clauses. There are arguably better ways to do error handling, but this one has been around for ages and given history and influences, has also found its way into JavaScript.
So, this is a valid way of doing error handling in JavaScript and TypeScript. But try to follow the same flow as with other programming languages, and annotate the error in your catch
clause.
try {
// something with Axios, for example
} catch(e: AxiosError) {
// ^^^^^^^^^^ Error 1196 💥
}
TypeScript will error with TS1196: Catch clause variable type annotation must be ‘any’ or ‘unknown’ if specified.
There are a couple of reasons for this:
1. Any type can be thrown #
In JavaScript, you are allowed to throw every expression. Of course, you can throw “exceptions” (or errors, as we call them in JavaScript), but it’s also possible to throw any other value:
throw "What a weird error"; // 👍
throw 404; // 👍
throw new Error("What a weird error"); // 👍
Since any valid value can be thrown, the possible values to catch are already broader than your usual sub-type of Error
.
2. There is only one catch clause in JavaScript #
JavaScript only has one catch
clause per try
statement. There have been proposals for multiple catch clauses and even conditional expressions in the distant past, but they never manifested. See JavaScript — the definitive guide for – hold it! – JavaScript 1.5 – what?!?
Instead, you should use this one catch
clause and do instanceof
and typeof
checks (Source):
try {
myroutine(); // There's a couple of errors thrown here
} catch (e) {
if (e instanceof TypeError) {
// A TypeError
} else if (e instanceof RangeError) {
// Handle the RangeError
} else if (e instanceof EvalError) {
// you guessed it: EvalError
} else if (typeof e === "string") {
// The error is a string
} else if (axios.isAxiosError(e)) {
// axios does an error check for us!
} else {
// everything else
logMyErrors(e);
}
}
Note: The example above is also the only correct way to narrow down types for catch
clauses in TypeScript.
And since all possible values can be thrown, and we only have one catch
clause per try
statement to handle them, the type range of e
is exceptionally broad.
3. Any exception can happen #
But hey, since you know about every error that can happen, wouldn’t be a proper union type with all possible “throwables” work just as well? In theory, yes. In practice, there is no way to tell which types the exception will have.
Next to all your user-defined exceptions and errors, the system might throw errors when something is wrong with the memory when it encountered a type mismatch or one of your functions has been undefined. A simple function call could exceed your call stack and cause the infamous stack overflow.
The broad set of possible values, the single catch
clause, and the uncertainty of errors that happen only allow two possible types for e
: any
and unknown
.
What about Promise rejections? #
The same is true if you reject a Promise. The only thing TypeScript allows you to specify is the type of a fulfilled Promise. A rejection can happen on your behalf, or through a system error:
const somePromise = () => new Promise((fulfil, reject) => {
if (someConditionIsValid()) {
fulfil(42);
} else {
reject("Oh no!");
}
});somePromise()
.then(val => console.log(val)) // val is number
.catch(e => {
console.log(e) // e can be anything, really.
})
It becomes clearer if you call the same promise in an asnyc
/await
flow:
try {
const z = await somePromise(); // z is number
} catch(e) {
// same thing, e can be anything!
}
Bottom line #
Error handling in JavaScript and TypeScript can be a “false friend” if you come from other programming languages with similar features. Be aware of the differences, and trust the TypeScript team and type checker to give you the correct control flow to make sure your errors are handled well enough.
Обработка ошибок
axios.get('/user/12345')
.catch(function (error) {
if (error.response) {
// Запрос был сделан, и сервер ответил кодом состояния, который
// выходит за пределы 2xx
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
} else if (error.request) {
// Запрос был сделан, но ответ не получен
// `error.request`- это экземпляр XMLHttpRequest в браузере и экземпляр
// http.ClientRequest в node.js
console.log(error.request);
} else {
// Произошло что-то при настройке запроса, вызвавшее ошибку
console.log('Error', error.message);
}
console.log(error.config);
});
Используя параметр конфигурации validateStatus
, вы можете определить HTTP-коды, которые должны вызывать ошибку.
axios.get('/user/12345', {
validateStatus: function (status) {
return status < 500; // Разрешить, если код состояния меньше 500
}
})
Используя toJSON
, вы получаете объект с дополнительной информацией об ошибке HTTP.
axios.get('/user/12345')
.catch(function (error) {
console.log(error.toJSON());
});