In the rapidly evolving world of web development, API interactions are important for creating responsive and reliable applications. It can be quite complicated when handling HTTP requests and responses, especially when dealing with error handling, data manipulation, and asynchronous operations. Here is where Axios comes into play-a powerful and versatile JavaScript library.
This blog is the ultimate guide to Axios from basic setup and simple GET/POST requests to advanced features such as interceptors, custom instances, and concurrent request handling. We’ll see how Axios simplifies API interactions, improves code readability, and enhances error management over native fetch().
Whether you are a front-end developer working with React or Vue.js, a back-end Node.js developer, or an automation tester willing to streamline API testing, in this guide, you will discover hands-on insights, realistic examples, and best practices through which you will be well-equipped for harnessing Axios for projects by the end of it, ensuring clean, maintainable, and efficient code.
- What is Axios?
- Why Use Axios Over fetch()?
- Installing and Setting Up Axios
- Importing and Configuring Axios
- Making HTTP Requests with Axios
- Handling Responses and Errors
- Advanced Axios Features
- Working with Concurrent Requests
- Practical Examples
- Comparison with Other Libraries
- Troubleshooting Common Issues
- Conclusion
What is Axios?
Axios is a popular, promise-based JavaScript library used to make HTTP requests from the browser or Node.js environments. It simplifies sending asynchronous HTTP requests and handling responses, making it a go-to tool for developers working with RESTful APIs. Axios supports modern JavaScript features like promises and async/await, making it particularly useful for asynchronous operations.
Key features of Axios include:
- Promise-based: Axios supports promises natively, making it easier to handle asynchronous code compared to traditional callbacks.
- Supports all HTTP methods: GET, POST, PUT, DELETE, PATCH, etc.
- Automatic JSON parsing: Responses are automatically parsed into JavaScript objects (in contrast to fetch(), which requires manual parsing).
- Error handling: Axios provides a streamlined error handling mechanism.
- Request and response interception: Interceptors allow you to modify requests or responses before they are handled.
Why Use Axios Over fetch()?
While the fetch() API provides a way to make HTTP requests, Axios offers several advantages over fetch() that make it the preferred choice in many development environments. Here are some of the key reasons developers opt for Axios:
Automatic JSON Parsing:
- In fetch(), you need to manually call .json() to parse the response body. In contrast, Axios automatically parses the response to JSON format if the content type is JSON, reducing boilerplate code.
- Example:
// Axios
axios.get('https://reqres.in/api/users?page=2').then(response => console.log(response.data)); // No need to parse
// Fetch
fetch('https://reqres.in/api/users?page=2').then(response
=> response.json()) // Manually parsing JSON
.then(data => console.log(data));
Built-in Error Handling:
- fetch() only rejects a promise if the network request itself fails (e.g., no internet connection), but it won’t reject HTTP error status codes (404, 500, etc.). This can lead to confusing error handling if the server returns a non-success status code.
- Axios automatically handles HTTP error responses as rejections, making it easier to catch and manage errors.
- Example:
// Axios
axios.get('https://reqres.in/api/users?page=2')
.catch(error => console.error(error.response.status));
// Fetch
fetch('https://reqres.in/api/users?page=2')
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.catch(error => console.error(error));
Request and Response Interceptors:
- Axios provides interceptors for requests and responses. This allows you to modify the request (e.g., add headers) or response before the application handles it, offering greater flexibility for tasks like authentication and logging.
Example (Adding an authentication token to requests):
// Axios request interceptor
axios.interceptors.request.use(config => {
config.headers['Authorization'] = `Bearer ${localStorage.getItem('token')}`;
return config;
});
Handling Concurrent Requests:
- Axios makes it easy to handle multiple concurrent requests using axios.all() and axios.spread(). This functionality allows you to execute multiple API requests in parallel and wait for all responses to come back before proceeding.
Example:
axios.all([
await axios.get(`https://reqres.in/api/users?page=2`),
await axios.get(`https://reqres.in/api/users/2`)
])
.then(
axios.spread((firstAPIResponse, secondAPIResponse) => {
// Validate the first API response
expect(firstAPIResponse.status).toBe(200);
console.log(
'First API Response Code: ' +
firstAPIResponse.status +
' ' +
firstAPIResponse.statusText
);
// Validate the second API response
expect(secondAPIResponse.status).toBe(200);
console.log(
'Second API Response Code: ' +
secondAPIResponse.status +
' ' +
secondAPIResponse.statusText
);
})
);
Request Cancellation:
- Axios supports request cancellation, allowing you to cancel an ongoing HTTP request if it’s no longer needed. This is useful in situations like navigating away from a page while an API request is still in progress.
- Example:
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
axios.get('/api/data', { cancelToken: source.token })
.catch(thrown => {
if (axios.isCancel(thrown)) {
console.log('Request canceled:', thrown.message);
}
});
// Cancel request
source.cancel('Operation canceled by the user.');
Supports Older Browsers:
- Axios works across all browsers, including older versions, which might not support fetch() or its full functionality.
Installing and Setting Up Axios
Installation of Axios
To begin, you’ll need to install Axios as a dependency in your WebdriverIO project.
Install Axios via npm or yarn:
Npm:
- npm install axios –save-dev
Or with yarn:
- yarn add axios –dev
Verify Installation
Ensure Axios is listed in your package.json dependencies:
"devDependencies": {
"axios": "^1.x.x",
"webdriverio": "^8.x.x",
...
}
Importing and Configuring Axios
There are several ways to integrate Axios into your WebdriverIO setup, depending on how you want to structure your project.
Option 1: Create an Axios Instance File
For better organization and code reuse, create a separate file for Axios configuration. Create a file (e.g., axiosConfig.js):
Create a file (e.g., axiosConfig.js):
// axiosConfig.js
import axios from 'axios';
// Create an Axios instance with default configurations
const apiClient = axios.create({
baseURL: 'https://api.example.com', // Set your base API URL here
timeout: 5000, // Request timeout in milliseconds
headers: {
'Content-Type': 'application/json' // Default content type for requests
}
});
// Export the Axios instance for use in other parts of the application
export default apiClient;
Import and Use Axios in Your WDIO Tests:
// exampleTest.spec.js
import apiClient from './axiosConfig';
describe('API Test with Axios', () => {
it('should fetch user data', async () => {
// Make an API call to fetch user data
const response = await apiClient.get('/users/1');
// Log the response data for debugging or further validation
console.log(response.data);
// Example assertion to validate response
// expect(response.status).toBe(200);
// expect(response.data.id).toBe(1);
});
});
Option 2: Integrate Axios Directly in Test Files
- If you prefer to keep your tests self-contained, you can import Axios directly within your WDIO spec files.
Create a test file where you will integrate Axios. For example, create a test/specs/axios.spec.js file.
const axios = require('axios'); // Import Axios
describe('Axios Integration with WDIO', () => {
it('should make a successful API request', async () => {
// Make an API request to fetch user data
const response = await axios.get('https://reqres.in/api/users/2');
// Assert the status code
expect(response.status).toBe(200);
// Assert the presence of specific properties in the response data
expect(response.data.data).toHaveProperty('id');
expect(response.data.data).toHaveProperty('email');
// Optional: Log the response data for debugging
console.log(response.data);
});
});
Option 3: Using Axios with Environment Variables
- For managing different environments (like dev, staging, and production), it’s a good practice to store the API base URL in environment variables.
Set Environment Variables:
- export BASE_URL=https://api.dev.example.com
Access Environment Variables in WDIO Tests:
// Set the base URL using an environment variable or default to a specific URL
const baseUrl = process.env.BASE_URL || 'https://api.example.com';
// Import Axios
import axios from 'axios';
// Create an Axios instance with the configured base URL and headers
const apiClient = axios.create({
baseURL: baseUrl,
headers: {
'Content-Type': 'application/json'
}
});
// Export the Axios instance for use in other parts of the application
export default apiClient;
Making HTTP Requests with Axios
Axios is a powerful HTTP client for JavaScript that simplifies making HTTP requests. Below is a comprehensive breakdown of how to use Axios to send various types of requests, including query parameters, and how to work with request bodies. This guide includes detailed examples and best practices.
GET, POST, PUT, PATCH, DELETE Requests
GET Request:
A GET request is used to retrieve data from a server. It doesn’t require a request body.
Example: Fetching User Data
import { default as axios } from 'axios';
describe('GET Method', () => {
it('User retrieves data from the server', async () => {
// Make the GET request to the API
try {
const response = await axios.get('https://reqres.in/api/users/2');
console.log(response.data); // Access and log the response data
// Optionally, add assertions for response validation
// expect(response.status).toBe(200);
} catch (error) {
console.error('Error fetching data:', error); // Handle any errors
}
});
});
Example with Query Parameters: To include query parameters, pass an object as the second argument.
import { default as axios } from 'axios';
describe('GET Method', () => {
it('User retrieves data from the server', async () => {
try {
// Make the GET request with query parameters
const response = await axios.get('https://reqres.in/api/users', {
params: {
page: 2,
total: 12
}
});
console.log(response.data); // Log the response data
// Optionally, add assertions for response validation
// expect(response.status).toBe(200);
} catch (error) {
console.error('Error fetching data:', error); // Handle any errors
}
});
});
This will generate a request to: https://reqres.in/api/users?page=2&total=12
POST Request
A POST request sends data to the server, often used to create new resources.
import { default as axios } from 'axios';
describe('POST Method', () => {
it('User creates data on the server', async () => {
try {
// Make the POST request to create a user
const response = await axios.post('https://reqres.in/api/users', {
name: 'Testing',
email: 'axios@yopmail.com'
});
console.log('User created:', response.data); // Log the response data
// Optionally, add assertions for response validation
// expect(response.status).toBe(201);
} catch (error) {
console.error('Error:', error); // Handle any errors
}
});
});
Handling Large JSON Payloads: Axios automatically converts JavaScript objects to JSON.
import { default as axios } from 'axios';
const userData = {
name: 'Testing',
email: 'axiosJson@yopmail.com'
};
describe('POST Method', () => {
it('User creates data on the server', async () => {
try {
// Make the POST request to create a user
const response = await axios.post('https://reqres.in/api/users', userData);
console.log('User created:', response.data); // Log the response data
// Optional: Add assertions to validate response
// expect(response.status).toBe(201); // Status code for successful creation
} catch (error) {
console.error('Error:', error); // Handle any errors
}
});
});
PUT Request
A PUT request updates an existing resource entirely. It usually requires the full data set.
Example: Updating User Information
import { default as axios } from 'axios';
describe('PUT Method', () => {
it('User updates data on the server', async () => {
try {
// Send PUT request to update user data
const response = await axios.put('https://reqres.in/api/users/1', {
name: 'Testing1',
email: 'updateaxios@yopmail.com'
});
console.log('User updated:', response.data); // Log the response data
// Optional: Add assertions to validate response
// expect(response.status).toBe(200); // Expecting a 200 status for a successful update
} catch (error) {
console.error('Error:', error); // Handle any errors
}
});
});
PATCH Request
A PATCH request partially updates an existing resource.
import { default as axios } from "axios";
describe('PATCH Method', () => {
it('User partially updates data on the server', async () => {
try {
// Send PATCH request to partially update user data (email in this case)
const response = await axios.patch('https://reqres.in/api/users/1', {
email: 'patchaxios@yopmail.com'
});
console.log('Partial Email Update:', response.data); // Log the updated response data
// Optional: Add assertions to check if the update was successful
// expect(response.status).toBe(200); // Expect a successful response with status code 200
} catch (error) {
console.error('Error:', error); // Handle any errors that may occur
}
});
});
DELETE Request
A DELETE request removes a resource.
import { default as axios } from "axios";
describe('DELETE Method', () => {
it('User successfully deletes data from the server', async () => {
try {
// Send DELETE request to remove user data
const response = await axios.delete('https://reqres.in/api/users/1');
console.log('User deleted:', response.data); // Log the response data
// Optional: Add assertions to verify the successful deletion
// expect(response.status).toBe(204); // Expect a successful deletion response with status code 204
} catch (error) {
console.error('Error:', error); // Handle any errors that may occur
}
});
});
Sending Query Parameters and Request Bodies
Query Parameters:
These are key-value pairs appended to the URL. Axios makes handling query strings easy:
const params = { page: 2, total: 12 };
axios.get('/api/products', { params })
.then(response => console.log(response.data));
Request Bodies: Mainly used with POST and PUT methods to send data.
const data = {
name: 'Test',
email: 'test@test.com'
};
axios.post('https://reqres.in/api/users', data);
Example: Fetching Data from an API
Let’s combine what we’ve learned into a practical example: Scenario: Fetch data from a public API and log specific fields.
import { default as axios } from "axios";
describe('GET Method', () => {
it('User successfully retrieves data from the server', async () => {
try {
// Send GET request to fetch posts
const response = await axios.get('https://jsonplaceholder.typicode.com/posts');
// Log the title of the first post
console.log('First Post Title:', response.data[0].title); // Fixed index to log the first post
// Optional: Add assertions to verify the response
// expect(response.status).toBe(200);
// expect(response.data.length).toBeGreaterThan(0);
} catch (error) {
console.error('API call failed:', error); // Proper error handling
}
});
});
Output:
Handling Dynamic Query Parameters:
Axios provides detailed error objects with useful properties:
import { default as axios } from "axios";
describe('GET Method', () => {
it('User retrieves data from the server', async () => {
try {
// Make GET request
const response = await axios.get('https://reqres.in/api/users/2'); // Fixed URL to match correct pattern
// Log the response data if needed
console.log('User Data:', response.data);
} catch (error) {
// Enhanced error handling
if (error.response) {
// Server returned a response with an error status code
console.error('Response Error:', error.response.data);
console.error('Response Status:', error.response.status);
console.error('Response Headers:', error.response.headers);
} else if (error.request) {
// No response received after making the request
console.error('Request Error:', error.request);
} else {
// Error occurred while setting up the request
console.error('Setup Error:', error.message);
}
}
});
});
Handling Responses and Errors
Axios simplifies HTTP requests in JavaScript and provides a built-in mechanism for handling both successful responses and errors. This ensures you can manage various scenarios gracefully, such as validating data, retrying failed requests, or showing custom error messages. Below is an in-depth breakdown, covering topics, subtopics, and relevant code examples.
Handling Success and Error Responses
Success Response:
Axios responses contain detailed information in a structured format:
- response.data: The response payload.
- response.status: HTTP status code (e.g., 200 for OK, 201 for Created).
- response.headers: The response headers.
- response.config: The original request configuration.
Example: Basic Success Handling
import { default as axios } from "axios";
describe('GET Method', () => {
it('User retrieves data from the server', async () => {
try {
const response = await axios.get('https://reqres.in/api/users/2');
// Log the response data and status
console.log('User data:', response.data); // Logs fetched data
console.log('Status:', response.status); // Logs status code (200)
} catch (error) {
console.error('Error fetching data:', error); // Logs any errors
}
});
});
Error Response:
Axios captures error details if a request fails. The error object has several properties:
- error.response: If the server responded with a status code outside 2xx.
- error.request: If the request was made, but no response received.
- error.message: Any errors occurring during request setup.
Example: Basic Error Handling
import { default as axios } from "axios";
describe('GET Method', () => {
it('User retrieves data from the server', async () => {
try {
const response = await axios.get('https://reqres.in/api/unknown/23');
console.log('Response data:', response.data); // Logs the response data
} catch (error) {
if (error.response) {
// Server responded with an error
console.error('Error status:', error.response.status); // Logs error status (e.g., 404)
console.error('Error data:', error.response.data); // Logs error details
} else if (error.request) {
// No response was received after the request
console.error('No response received:', error.request);
} else {
// Error in request setup
console.error('Request error:', error.message);
}
}
});
});
Status Codes and Custom Error Messages
Error handling based on status codes allows you to manage different types of errors gracefully, providing more informative feedback to users or logs. Each HTTP status code represents a different type of response from the server, and understanding these can help you build more robust applications. Let’s dive deeper into this concept with a more detailed breakdown and examples.
Understanding HTTP Status Codes:
HTTP status codes are standardized responses from the server to indicate the result of a client’s request. They are categorized into different classes:
- 1xx (Informational): Request received, continuing process.
- 2xx (Success): Request successfully received, understood, and accepted.
- 3xx (Redirection): Further action needs to be taken to complete the request.
- 4xx (Client Errors): Errors that occur due to the client’s request.
- 5xx (Server Errors): Errors on the server side, often indicating issues with the server itself.
For more detailed information about the HTTP response status code please visit this website: HTTP Status code overview
Handling Specific Status Codes in Axios
When you make a request using Axios, the server’s response might fall into the 4xx or 5xx range. Customizing error messages based on these status codes improves the clarity of the feedback you provide.
Code Example: Handling Different Error Scenarios
import axios from 'axios';
// Function to handle different HTTP errors
function handleError(error) {
if (error.response) {
// Server responded with a status outside the 2xx range
switch (error.response.status) {
case 400:
console.error('Bad Request: Please check your request parameters.');
break;
case 401:
console.error('Unauthorized: Access is denied due to invalid credentials.');
break;
case 403:
console.error('Forbidden: You do not have permission to access this resource.');
break;
case 404:
console.error('User Not Found: The requested resource does not exist.');
break;
case 500:
console.error('Internal Server Error: There is an issue with the server.');
break;
default:
console.error(`Unexpected Error: ${error.response.statusText}`);
}
} else if (error.request) {
// Request was made, but no response received
console.error('No Response: Unable to contact the server.');
} else {
// Something else caused an error
console.error('Error:', error.message);
}
}
Detailed Breakdown: Key Sections of the Code
- error.response Handling:
- This block is executed if the server responds with a status code outside the 2xx range (client or server error).
- Why It’s Useful: You can tailor user messages based on specific status codes, improving the user experience and helping with debugging.
- error.request Handling:
- This block executes if the request was sent but no response was received. Common causes include network errors or server unavailability.
- Example Scenario: The server might be down, or there’s a network issue preventing the response.
- General Error (error.message) Handling:
- This block is for any errors that occur during the setup of the request, such as incorrect configurations or syntax errors.
Why Custom Error Messages Are Important:
- User Experience: Clear, descriptive messages help users understand what went wrong and how to resolve it.
- Debugging: Differentiate between client-side and server-side issues, making it easier to diagnose and fix problems.
- Error Recovery: Some errors, like network timeouts, may be retried programmatically if the status code indicates a temporary issue.
Using .catch() and .then()
Understanding Axios Request Chaining
Axios allows you to perform multiple HTTP requests in sequence, ensuring each request executes after the previous one has successfully completed. This is achieved through promise chaining using .then() and .catch().
- .then() handles the resolved promise (successful response).
- .catch() manages the rejected promise (error response).
- How .then() Works:
The .then() method handles the successful resolution of a promise. Each .then() returns a new promise, allowing you to chain multiple requests.
- Chaining Multiple .then() Blocks:
Each .then() processes the response from the previous request and passes data down the chain.
- Error Handling with .catch():
The .catch() method handles any errors that occur in the entire promise chain. It catches errors from any .then() block or request failure.
Types of Errors Handled:
- Error.response:
- Cause: The server responds with an error status code (e.g., 404, 500).
- Example:
- console.error(‘Response error:’,error.response.status);
- Error.request:
- Cause: The request was sent but did not receive a response (network issues, timeout).
- Example:
- console.error(‘No response received:’, error.request);
- General Errors (error.message):
- Cause: Issues with the request configuration, invalid URL, or syntax errors.
- Example:
- console.error(‘Error message:’, error.message);
Example: Chaining Multiple Requests
import { default as axios } from "axios";
describe('GET Method', () => {
it('User retrieves data from the server', async () => {
try {
// First request
const firstPageResponse = await axios.get('https://reqres.in/api/users/1');
console.log('First page of users:', firstPageResponse.data);
// Second request
const secondPageResponse = await axios.get('https://reqres.in/api/users/2');
console.log('Second page of users:', secondPageResponse.data);
} catch (error) {
console.error('Error in the request chain:', error.message);
}
});
});
Detailed Breakdown: Key Sections of the Code
First axios.get() Request:
await axios.get('https://reqres.in/api/users/1')
.then(response => {
console.log('First page of users:', response.data);
return axios.get('https://reqres.in/api/users/2'); // Fetch next page
})
- Purpose: Initiates a GET request to fetch the first page of users.
- Why It’s Useful: This establishes the initial data retrieval step in a sequence. The fetched data can be processed or passed to the next request.
- What Happens: If the request is successful, it logs the first page’s data and returns another Axios promise to fetch the second page. Returning this promise is crucial to continue the chain.
Second .then() Block:
.then(response => {
console.log('Second page of users:', response.data);
})
- Purpose: Handles the response from the second request.
- Why It’s Useful: Allows chaining multiple dependent requests sequentially. Data from the first request can influence or prepare for the next request.
- What Happens: When the second request completes, it logs the second page’s data. This ensures each page’s data is processed in sequence, maintaining logical order.
catch() Block:
.catch(error => {
console.error('Error in the request chain:', error.message);
});
- Purpose: Catches errors that occur in any part of the request chain.
- Why It’s Useful: Centralized error handling ensures that any failure during the sequence is managed gracefully, preventing unhandled exceptions.
- What Happens:
- If error.response exists: The server responded with an error status code (e.g., 404 or 500).
- If error.request exists: The request was sent but did not receive a response (e.g., network issue).
- For other errors: Issues with setting up the request, like configuration errors or invalid URLs.
- Example Scenario: If the first or second API endpoint is unavailable, this block logs the error message, providing diagnostic information for debugging.
Advanced Axios Features
Axios is a robust JavaScript library for making HTTP requests, providing advanced capabilities to handle complex scenarios effectively. Below are key topics and subtopics that dive deeper into these features with examples:
Axios Interceptors (Request and Response)
Purpose:
Interceptors allow you to modify requests or responses before they are handled by .then() or .catch().
Types:
- Request Interceptors: Modify requests before they are sent.
- Response Interceptors: Modify responses before they are passed to .then().
Example:
import axios from 'axios';
// Add a request interceptor
axios.interceptors.request.use(
(config) => {
// Modify the request: Add authentication token to headers
config.headers.Authorization = 'Bearer YOUR_TOKEN';
console.log('Request made with Authorization Header:', config.headers);
return config;
},
(error) => {
// Handle request errors
console.error('Request Error:', error);
return Promise.reject(error);
}
);
// Add a response interceptor
axios.interceptors.response.use(
(response) => {
// Modify response data if needed
console.log('Response data:', response.data); // Can manipulate data here if necessary
return response.data; // Only return the modified data, not the entire response
},
(error) => {
// Handle response errors globally
if (error.response) {
console.error('Error Response:', error.response.status, error.response.data);
} else if (error.request) {
console.error('No Response: ', error.request);
} else {
console.error('Error:', error.message);
}
return Promise.reject(error); // Continue propagating the error
}
);
// Example API call using Axios to test interceptors
axios.get('https://reqres.in/api/users/1')
.then((response) => {
console.log('Data from server:', response);
})
.catch((error) => {
console.error('Error during the request:', error);
});
Use Cases:
- Automatically attach authentication tokens.
- Log or modify responses globally.
- Centralized error handling.
Creating and Using Axios Instances
Purpose:
Create custom Axios instances with default configurations, such as base URLs, headers, or timeout settings. This is useful when dealing with multiple APIs or needing different configurations for various parts of your application.
Example:
import axios from 'axios';
// Create an Axios instance with custom settings
const apiClient = axios.create({
baseURL: 'https://reqres.in/api', // Base URL for all requests
timeout: 5000, // Set timeout to 5 seconds
headers: { 'Content-Type': 'application/json' } // Default content type is JSON
});
// Use the custom Axios instance to make a GET request
apiClient.get('/users?page=2')
.then(response => {
console.log('Response data:', response.data); // Log the response data from the API
})
.catch(error => {
console.error('API error:', error.message); // Log the error message if the request fails
});
Key Benefits:
- Simplifies code by setting defaults once.
- Helps maintain clean and consistent API calls.
- Easier to manage different services or microservices.
Custom Headers and Configuration Defaults
Purpose:
Set default headers or other configuration options globally or for individual requests.
Global Default Configuration:
// Set global defaults
axios.defaults.baseURL = 'https://reqres.in/api';
axios.defaults.headers.common['Authorization'] = 'Bearer YOUR_TOKEN';
axios.defaults.headers.post['Content-Type'] = 'application/json';
Per-Request Configuration:
// Custom headers for a single request
axios.get('/users', {
headers: {
'Authorization': 'Bearer DIFFERENT_TOKEN'
}
})
.then(response => {
console.log(response.data);
});
Working with Concurrent Requests
Concurrent requests allow you to perform multiple HTTP operations in parallel, optimizing performance and reducing wait times for data retrieval. This is especially useful when dealing with different endpoints or when multiple pieces of data are required simultaneously.
Using axios.all() and axios.spread()
Why Use Concurrent Requests?
Benefits:
- Improved Performance: Fetch multiple resources at once, reducing overall waiting time.
- Data Aggregation: Gather and process related data in a single operation.
- Simplified Code: Manage multiple requests together, reducing redundancy.
Using axios.all()
axios.all() accepts an array of promises and returns a single promise that resolves when all input promises resolve.
Basic Syntax:
axios.all([
axios.get('https://reqres.in/api/users/1'),
axios.get('https://reqres.in/api/users/2')
])
.then(responseArray => {
console.log('User 1 Data:', responseArray[0].data);
console.log('User 2 Data:', responseArray[1].data);
})
.catch(error => {
console.error('Error fetching data:', error);
});
Explanation:
- axios.all(): Initiates multiple requests simultaneously.
- responseArray: Contains responses in the order of the original array.
- Error Handling: A single .catch() handles errors for any failed request.
Managing Multiple API Calls Efficiently
Combining axios.all() with axios.spread()
axios.spread() is a helper function to unpack the response array into separate variables, making the code cleaner.
Example:
axios.all([
axios.get('https://reqres.in/api/users/1'),
axios.get('https://reqres.in/api/users/2')
])
.then(axios.spread((user1, user2) => {
console.log('User 1 Data:', user1.data);
console.log('User 2 Data:', user2.data);
}))
.catch(error => {
console.error('Error:', error.message);
});
Explanation:
- axios.spread(): Destructures the array into individual response objects (user1 and user2).
- Readable Flow: Makes handling multiple responses more intuitive and avoids manual array indexing.
Error Handling in Concurrent Requests
When using multiple requests, error handling becomes crucial. If any request fails, the .catch() block will execute.
Basic Error Handling Example:
axios.all([
axios.get('/endpoint1'),
axios.get('/endpoint2')
])
.then(axios.spread((res1, res2) => {
// Handle successful responses
}))
.catch(error => {
console.error('One or more requests failed:', error.message);
});
Advanced Error Handling:
To ensure partial success handling, catch errors within individual promises:
axios.all([
axios.get('/endpoint1').catch(err => ({ error: err.message })),
axios.get('/endpoint2').catch(err => ({ error: err.message }))
])
.then(results => {
results.forEach((result, index) => {
if (result.error) {
console.error(`Request ${index + 1} failed:`, result.error);
} else {
console.log(`Request ${index + 1} data:`, result.data);
}
});
});
Advanced Use Cases and Patterns
Aggregating Data from Multiple Endpoints:
Combine data from different sources before processing:
axios.all([
axios.get('/users/1'),
axios.get('/posts?userId=1'),
axios.get('/comments?userId=1')
])
.then(axios.spread((user, posts, comments) => {
const aggregatedData = {
user: user.data,
posts: posts.data,
comments: comments.data
};
console.log(aggregatedData);
}));
Dependent Requests:
Fetch related data after initial responses:
axios.get('/user/1')
.then(user => axios.get(`/posts?userId=${user.data.id}`))
.then(posts => console.log('Posts:', posts.data))
.catch(error => console.error('Error:', error.message));
Best Practices:
- Handle Partial Failures Gracefully: Ensure one failing request doesn’t crash the entire application.
- Timeout Management: Set appropriate timeouts for each request to avoid hanging connections.
- Optimize Network Requests: Minimize unnecessary API calls and ensure efficient payload handling.
Practical Examples
Axios is a powerful library for handling HTTP requests in JavaScript, providing essential functionality for various real-world scenarios. Let’s delve into practical examples that demonstrate its capabilities, organized by key topics and subtopics:
Uploading Files with Axios
Overview: Axios simplifies file uploads by handling multipart form data and enabling progress tracking. This feature is crucial for applications where users need to upload images, videos, or documents.
Example:
import axios from 'axios';
import fs from 'fs';
import FormData from 'form-data';
describe('File Upload Test with Additional Fields', () => {
it('should upload a file with additional parameters', async () => {
// Define the file path
const filePath = '/pathtofile/filepath.txt'; // Replace with your file path
// Create form data
const formData = new FormData();
formData.append('file', fs.createReadStream(filePath));
formData.append('field1', 'value1'); // Example of an additional field
try {
// Make an Axios POST request to upload the file with additional parameters
const response = await axios.post('https://file.io', formData, {
headers: {
...formData.getHeaders(), // Automatically sets 'Content-Type'
},
});
// Log the response data
console.log('Upload Response:', response.data);
// Assertions to validate response
expect(response.status).toBe(200); // Check for successful status code
expect(response.data.success).toBe(true);
expect(response.data.link).toBeDefined();
} catch (error) {
// Handle errors during the request
console.error('Error uploading file:', error.response ? error.response.data : error.message);
throw error;
}
});
});
Output:
Key Points:
- FormData API: Used to construct the form data, appending both the file and additional fields.
- Automatic Headers: formData.getHeaders() automatically sets the correct Content-Type for multipart/form-data.
- Error Handling: Errors are captured, and the response or error message is logged for debugging.
- Assertions: Verifies the success status and checks if a file link is returned upon successful upload.
- File Upload: Uses fs.createReadStream() to read the file from the local system and send it in the request.
Handling Pagination and Infinite Scrolling
Overview: Pagination is essential for displaying large datasets efficiently. Axios helps fetch paginated data and manage state seamlessly.
Example:
import axios from 'axios';
describe('Paginated Data Fetch Test', () => {
let currentPage = 1;
it('should fetch paginated data and display users', async () => {
try {
// Fetch the paginated data
const response = await axios.get(`https://reqres.in/api/users?page=${currentPage}`);
// Display the data in the console
await displayData(response.data.data);
// Assert that data is returned for the current page
expect(response.data.data.length).toBeGreaterThan(0);
expect(response.data.page).toBe(currentPage);
// Check if there are more pages to load
if (response.data.total_pages > currentPage) {
currentPage++;
// Optionally, trigger another fetch based on scroll or a button click
console.log('Fetching more data...');
// You can implement the logic for fetching more data here (e.g., recursive fetch)
}
} catch (error) {
console.error('Error fetching data:', error.response ? error.response.data : error.message);
throw error;
}
});
// Function to render data on the page
async function displayData(users) {
users.forEach(user => {
console.log(`User: ${user.first_name} ${user.last_name}`);
});
}
});
Output:
Key Points:
- WDIO Test Structure: The test is structured using describe and it blocks, with assertions built into the test body.
- Assertions:
- Ensure that data is returned for the current page and the page number is correct.
- Check that the data length is greater than 0 for pagination validation.
- Fetching More Data: If more pages are available, the test increments the page number and logs that additional data is being fetched. You can extend this logic for simulating UI actions like scrolling or button clicks (not included here but could be added for UI testing).
- Error Handling: The error handling ensures that any issues with the request are logged and thrown, causing the test to fail if the fetch is unsuccessful.
- Dynamic URLs: Adjust the API URL to include the current page parameter.
- Data Handling: Response data is processed and displayed, updating the current page.
- Scalability: Supports infinite scrolling by continuously loading more data as the user scrolls.
Real-World Example: Testing Weather API with Assertions
- Prerequisites:
Make sure you have the following dependencies installed:
- axios for HTTP requests.
- chai for assertions.
- @wdio/mocha-framework or similar setup for test execution.
Install them using:
- npm install axios chai @wdio/mocha-framework –save-dev
Weather API Test Example
In this example, we will validate:
- Successful API response for a valid city.
- Correct temperature and weather description format.
- Error handling for invalid input or API key.
Example:
import axios from 'axios';
import { expect } from 'chai';
require('dotenv').config(); // Load environment variables
describe('Weather API Tests', () => {
const baseURL = 'https://api.openweathermap.org/data/2.5/weather';
let apiKey;
before(() => {
apiKey = process.env.API_KEY; // Use environment variable for the API key
});
it('should fetch weather data successfully for a valid city', async () => {
const city = 'New York';
try {
// API Request
const response = await axios.get(`${baseURL}?q=${city}&appid=${apiKey}`);
// Assertions for status code
expect(response.status).to.equal(200);
// Assertions for response data
const weatherData = response.data;
expect(weatherData).to.have.property('main');
expect(weatherData.main).to.have.property('temp').that.is.a('number');
expect(weatherData.weather[0]).to.have.property('description').that.is.a('string');
console.log(`Temperature in ${city}: ${weatherData.main.temp}°C`);
console.log(`Weather Description: ${weatherData.weather[0].description}`);
} catch (error) {
throw new Error('API request failed: ' + error.message);
}
});
it('should return an error for an invalid city name', async () => {
const invalidCity = 'InvalidCity123';
try {
await axios.get(`${baseURL}?q=${invalidCity}&appid=${apiKey}`);
} catch (error) {
// Assertions for error response
expect(error.response.status).to.equal(404);
expect(error.response.data).to.have.property('message').that.includes('city not found');
console.log('Error Message:', error.response.data.message);
}
});
it('should return an error for invalid API key', async () => {
const city = 'London';
const invalidApiKey = 'invalid_api_key';
try {
await axios.get(`${baseURL}?q=${city}&appid=${invalidApiKey}`);
} catch (error) {
// Assertions for unauthorized status
expect(error.response.status).to.equal(401);
expect(error.response.data).to.have.property('message').that.includes('invalid API key');
console.log('Error Message:', error.response.data.message);
}
});
});
Output:
- Successful test: Logs temperature and weather details.
- Failed test: Captures and displays error messages.
- Assertions ensure data accuracy and error handling.
Key Breakdown:
- Setup:
- Use axios for API requests.
- Use chai for assertion checks.
- Successful API Test:
- Check the status code 200.
- Verifies temperature and weather description format.
- Invalid City Test:
- Verifies 404 error for invalid city names.
- Ensures the response contains a meaningful error message.
- Invalid API Key Test:
- Ensures 401 error for unauthorized API access.
- Validates the error message for invalid credentials.
Best Practices and Tips for Real-World Use Cases
Efficient Error Handling:
- Use try-catch blocks in async/await calls to handle exceptions cleanly.
try {
const response = await axios.get('/endpoint');
console.log(response.data);
} catch (error) {
console.error('Request failed:', error);
}
Request Retry Logic:
- Implement retry mechanisms for unreliable networks.
const axiosRetry = require('axios-retry');
axiosRetry(axios, { retries: 3 });
Environment-Based Configuration:
- Set up different base URLs for development and production environments.
axios.defaults.baseURL = process.env.NODE_ENV === 'production' ? 'https://api.prod.com' : 'http://localhost:3000';
Caching Data:
- Use client-side caching strategies to reduce redundant network requests and improve performance.
Comparison with Other Libraries
Axios vs. Fetch vs. Superagent
When choosing an HTTP client for JavaScript projects, developers often consider Axios, the native Fetch API, or libraries like Superagent. Each tool offers unique features, and selecting the right one depends on your specific project needs. Let’s dive into a detailed comparison, covering key aspects and providing code examples for each.
Overview of Each Library
Axios
- Description: A promise-based HTTP client for the browser and Node.js. It simplifies HTTP requests, handles errors more gracefully, and supports features like interceptors and automatic JSON transformation.
- Key Feature: Supports request/response transformations, interceptors, and easier handling of timeouts.
Fetch API
- Description: A modern native browser API for making HTTP requests. Fetch is built into JavaScript and operates using promises.
- Key Feature: No need for external libraries, built-in support in modern browsers.
Superagent
- Description: A robust HTTP client that can be used in both Node.js and browsers. It provides a flexible API with chaining methods.
- Key Feature: Middleware and plugin support, chaining API for request setup.
Comparison Criteria
Syntax and Ease of Use
Axios Example:
axios.get('https://reqres.in/api/users/2')
.then(response => console.log(response.data))
.catch(error => console.error(error));
Fetch Example:
fetch('https://reqres.in/api/users/2')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error));
Superagent Example:
superagent.get('https://reqres.in/api/users/2')
.end((err, res) => {
if (err) console.error(err);
else console.log(res.body);
});
Key Takeaway:
- Axios and Superagent simplify request handling and error management with cleaner syntax.
- Fetch requires explicit steps for converting responses to JSON and handling errors.
- Error Handling
Axios provides more intuitive error handling:
await axios.get('https://reqres.in/api/users/path/2')
.catch(error => {
if (error.response) {
// Server responded with status code outside 2xx
console.error('Data:', error.response.data);
} else if (error.request) {
// No response received
console.error('Request:', error.request);
} else {
console.error('Error:', error.message);
}
});
Fetch needs manual handling for non-2xx responses:
fetch('https://reqres.in/api/users/path/2') // Fetch request to the given URL
.then(response => {
if (!response.ok) { // Check if the response status is not OK (i.e., not in the 2xx range)
throw new Error(`HTTP error! Status: ${response.status}`); // Throw an error with the status code
}
return response.json(); // If status is OK, parse the response as JSON
})
.catch(error => console.error('Fetch Error:', error)); // Catch and log any errors (e.g., network errors, thrown errors)
Superagent
superagent.get('https://api.example.com/data')
.then(response => {
console.log(response.body);
})
.catch(error => {
if (error.response) {
console.error('Response Error:', error.status, error.response.body);
} else {
console.error('Request Error:', error.message);
}
});
Key Takeaway:
- Axios automatically rejects promises for non-2xx responses, simplifying error handling.
- Fetch requires additional checks for response status, making it less straightforward.
- Superagent: Errors are caught explicitly through .catch(), but it provides more granular control over responses and can handle different status codes via .ok() or .statusType() checks.
- Request/Response Interceptors
Axios has built-in support for interceptors:
axios.interceptors.request.use(request => {
console.log('Starting Request', request);
return request;
});
axios.interceptors.response.use(response => {
console.log('Response:', response);
return response;
});
Fetch lacks native interceptors, requiring custom wrapper functions:
function fetchWithLogging(url, options) {
console.log('Starting Request', url);
return fetch(url, options).then(response => {
console.log('Response:', response);
return response;
});
}
Key Takeaway:
- Axios makes it easier to implement request/response interceptors for logging, authentication, etc.
- Fetch requires custom code for similar functionality.
- Browser and Node.js Support
- Axios: Works in both browsers and Node.js without significant differences.
- Fetch: Browser-native, but in Node.js requires the node-fetch package.
- Superagent: Works well in both environments, especially useful for Node.js applications.
Advanced Features
Feature | Axios | Fetch | Superagent |
JSON Transformation | Automatic | Manual | Automatic |
Request Cancellation | Supports CancelToken | Limited support with AbortController | Middleware-based |
Timeouts | Configurable | Needs AbortController | Configurable |
Interceptors | Yes | No (requires custom code) | Yes |
File Uploads | Simplified with FormData | Requires FormData | Built-in support |
Best Scenarios for Each Library
Scenario | Best Choice |
Simple browser-based HTTP requests | Fetch (lightweight, no dependencies) |
Complex apps needing interceptors | Axios (built-in interceptors) |
Node.js applications | Axios or Superagent |
File uploads/downloads | Axios or Superagent |
Custom middleware needs | Superagent (supports plugins) |
Keynote:
- Axios is an excellent choice for developers who need robust error handling, interceptors, and easy JSON parsing.
- Fetch is perfect for lightweight, modern browser projects where you want minimal dependencies.
- Superagent offers a flexible, middleware-based approach, particularly useful in complex Node.js environments.
By understanding the strengths and weaknesses of each library, you can make an informed decision about which to use for your specific needs!
Troubleshooting Common Issues
When working with Axios for HTTP requests, you may encounter challenges such as CORS errors, timeouts, and the need for retries. Let’s break down these common issues and provide clear solutions and examples to help you navigate them effectively.
Debugging and Solving CORS Errors
What is CORS?
CORS (Cross-Origin Resource Sharing) is a security feature enforced by browsers to prevent unauthorized access to resources from a different origin. When the server doesn’t permit cross-origin requests, the browser blocks the request, and you see a CORS error.
Common Error:
Access to XMLHttpRequest at 'http://example.com' from origin 'http://localhost:3000' has been blocked by CORS policy.
Causes:
- The server doesn’t include the necessary Access-Control-Allow-Origin headers.
- The client is attempting to access a resource from a different origin (domain, protocol, or port).
Solutions: Server-Side Configuration: Ensure that the backend server includes CORS headers. Example for an Express.js server:
const express = require('express');
const cors = require('cors');
const app = express();
app.use(cors()); // Allow all origins
// OR specify allowed origins
app.use(cors({ origin: 'http://localhost:3000' }));
Proxy Setup (Client-Side):If you cannot control the server, set up a proxy in development. For example, in React, add this to package.json:
“proxy”: “http://example.com”
Then, requests to /api will be forwarded to the specified server.
Browser Extension: For development purposes, use a CORS plugin to bypass restrictions (not recommended for production).
Handling Timeouts and Retries
Handling Timeouts
What are Timeouts?
A timeout occurs when a request takes too long to get a response. By default, Axios has no timeout, but you can configure it.
Setting a Timeout:
await axios.get('https://reqres.in/api/users/2', {
timeout: 5000 })
// 5 seconds
.then(response => console.log(response))
.catch(error => {
if (error.code === 'ECONNABORTED') {
console.error('Request timeout!');
}
})
Advanced Timeout Handling:
- Global Timeout: Set a default timeout for all requests:
axios.defaults.timeout = 10000; // 10 seconds
- Abort Controller: Cancel a request after a timeout:
const controller = new AbortController();
setTimeout(() => controller.abort(), 5000);
await axios.get('https://reqres.in/api/users/2', { signal: controller.signal })
.catch(error => {
if (error.message === 'canceled') {
console.error('Request canceled due to timeout');
}
});
Handling Retries
Why Retries?
Retries are useful when dealing with intermittent network issues or server hiccups.
Manual Retry Example:
const axios = require('axios');
async function fetchDataWithRetry(url, retries = 3, delay = 1000) {
for (let i = 0; i < retries; i++) {
try {
// Attempt to fetch data
const response = await axios.get(url);
return response.data; // Return data if successful
} catch (error) {
if (i === retries - 1) {
// Throw error on final attempt
throw error;
}
// Log retry attempt and wait before retrying
console.warn(`Retrying... (${i + 1}/${retries})`);
await new Promise(res => setTimeout(res, delay)); // Wait before retry
}
}
}
// Usage
fetchDataWithRetry('https://reqres.in/api/users/2')
.then(data => console.log('Data:', data)) // Log data if successful
.catch(error => console.error('Request failed:', error)); // Log error if all attempts fail
Using Axios Retry Library:
Install the axios-retry library:npm install axios-retry
Configure retries:
const axiosRetry = require('axios-retry');
axiosRetry(axios, { retries: 3, retryDelay: (retryCount) => {
console.log(`Retry attempt: ${retryCount}`);
return retryCount * 1000; // Exponential backoff
}});
Key Takeaways:
- CORS errors: Address them on the server side or use client-side proxies.
- Timeouts: Set appropriate limits to avoid hanging requests and handle timeouts gracefully.
- Retries: Implement retries to make applications more resilient to transient failures.
By understanding and addressing these common issues in Axios, you ensure more robust and reliable API interactions in your applications.
Conclusion
Axios is a powerful and versatile library that simplifies handling HTTP requests in JavaScript applications. From basic installations and configurations to making advanced HTTP requests, Axios offers a clean and intuitive syntax that enhances the developer experience. In this blog, we explored the core aspects of Axios, covering everything from simple GET and POST requests to more complex features like interceptors, concurrent requests, and custom configurations.
We’ve also delved into practical implementations such as file uploads, pagination, and building a real-world weather app, demonstrating how Axios can streamline complex API interactions. By comparing Axios with other libraries like Fetch and Superagent, we highlighted scenarios where each tool excels, helping you make informed decisions based on your project’s needs.
Additionally, we addressed common challenges such as CORS errors, timeouts, and retries, providing actionable solutions to troubleshoot and enhance the reliability of your HTTP requests. These insights ensure that your applications remain robust, efficient, and maintainable.
Mastering Axios opens up numerous possibilities for managing data flow in web and mobile applications. With its extensive feature set and flexibility, Axios remains a top choice for developers seeking to handle API communication seamlessly and effectively.
Witness how our meticulous approach and cutting-edge solutions elevated quality and performance to new heights. Begin your journey into the world of software testing excellence. To know more refer to Tools & Technologies & QA Services.
If you would like to learn more about the awesome services we provide, be sure to reach out.
Happy Testing 🙂