Lesson 6: Advanced API Interactions with axios
Workplace Context
While Node’s built-in fetch
is capable, real-world applications often require more complex interactions and a more streamlined development experience. Your team needs to send structured data to an external API, handle authentication for every request, and manage different API configurations cleanly. For these tasks, a dedicated HTTP client library like axios
is the industry standard.
Learning Objectives
By the end of this lesson, you will be able to:
- Explain the advantages of using
axios
overfetch
. - Refactor a
fetch
request to useaxios
. - Construct and send
POST
requests with a data payload usingaxios
. - Create pre-configured
axios
instances for specific APIs usingaxios.create()
. - Use interceptors to globally modify requests and responses.
axios
vs. fetch
: Why Use a Library?
axios
is a very popular, Promise-based HTTP client. While it does the same fundamental job as fetch
- making HTTP requests - it offers several quality-of-life improvements that developers prefer for professional projects:
Feature | fetch() | axios |
---|---|---|
JSON Data | Requires two steps: one to get the response, a second to parse the body with response.json() . | Automatic. axios automatically stringifies outgoing data and parses incoming data. The result is in response.data . |
Error Handling | Only rejects on network errors. For “bad” HTTP statuses (like 404 or 500), you must manually check response.ok or response.status . | Unified. Rejects on both network errors AND bad HTTP statuses. This lets you catch all errors in a single .catch() block. |
Request Configuration | Configuration (headers, method, body) is passed in a second, sometimes verbose, options object. | Provides clear, top-level methods like axios.post() and axios.delete() that are more intuitive. |
Request Timeout | Supported via an AbortController , which can be complex to set up. | Supported with a simple timeout property in the configuration. |
To get started, you must first install axios
:
npm install axios
Refactoring fetch
to axios
Let’s refactor the GET /users
example from the previous lesson. Notice how axios
simplifies the process by removing the need for the .json()
parsing step and unifying error handling.
const express = require('express');
const axios = require('axios'); // 1. Import axios
const app = express();
const port = 3000;
app.get('/users', async (req, res) => {
try {
// 2. Await the axios GET request.
const response = await axios.get('https://jsonplaceholder.typicode.com/users');
// 3. The data is already parsed and available in `response.data`.
res.json(response.data);
} catch (error) {
// 4. This single catch block handles everything:
// - Network errors (can't connect)
// - HTTP status errors (404, 500, etc.)
if (error.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
console.error('API Error:', error.response.status, error.response.data);
res.status(error.response.status).json({ message: 'Error fetching data from external API.' });
} else {
// Something happened in setting up the request that triggered an Error
console.error('Network Error:', error.message);
res.status(500).json({ message: 'A network error occurred.' });
}
}
});
app.listen(port, () => {
console.log(`Server is running at http://localhost:${port}`);
});
Making POST
Requests with axios
Sending data is where axios
truly shines in its simplicity. To make a POST
request, you use axios.post()
. The first argument is the URL, and the second is the data payload (as a JavaScript object). axios
handles converting this object to a JSON string for you.
const express = require('express');
const axios = require('axios');
const app = express();
const port = 3000;
// Middleware to parse the body of OUR server's incoming requests
app.use(express.json());
app.post('/posts', async (req, res) => {
try {
// The data we want to send, perhaps from our own client
const newPostData = req.body;
// The second argument to axios.post is the request body.
// axios automatically sets the Content-Type header to application/json.
const response = await axios.post(
'https://jsonplaceholder.typicode.com/posts',
newPostData
);
// Send back the newly created post from the external API
res.status(201).json(response.data);
} catch (error) {
console.error('Error creating post:', error.message);
res.status(500).json({ message: 'Failed to create post.' });
}
});
app.listen(port, () => {
console.log(`Server is running at http://localhost:${port}`);
});
Creating a Configured Instance
In a real application, you’ll often make many requests to the same API. Repeating the base URL and common headers (like an Authorization
token) in every single call is repetitive and error-prone.
axios
solves this with axios.create()
, which lets you create a new, pre-configured instance of the axios
client.
Use Case: Imagine you’re building a service that heavily interacts with the GitHub API. You can create a dedicated githubClient
for all your requests.
const axios = require('axios');
const githubClient = axios.create({
baseURL: 'https://api.github.com',
headers: {
'Accept': 'application/vnd.github.v3+json',
// Note: In a real app, this token would come from an environment variable!
'Authorization': 'token YOUR_PERSONAL_ACCESS_TOKEN'
}
});
module.exports = githubClient;
Now, in other parts of your application, you can use this instance without repeating the configuration:
// ... other requires
const githubClient = require('./api/githubClient');
app.get('/repos/:username', async (req, res) => {
try {
// No need to write the full URL! Just the endpoint.
// Headers are also automatically included.
const response = await githubClient.get(`/users/${req.params.username}/repos`);
res.json(response.data);
} catch (error) {
// ... error handling
}
});
Using Interceptors
Interceptors are one of the most powerful features of axios
. They allow you to run your code or modify requests and responses globally before they are sent or handled by then
or catch
.
Request Interceptors
A request interceptor runs before a request is sent. It’s perfect for tasks that need to happen on every request, like logging or dynamically adding a header.
// Add a request interceptor to our instance
githubClient.interceptors.request.use(config => {
// This function runs before any request is sent using githubClient
console.log(`Sending request to: ${config.baseURL}${config.url}`);
// You must always return the config object, otherwise the request will fail
return config;
}, error => {
// Handle request error
return Promise.reject(error);
});
Response Interceptors
A response interceptor runs before the then
or catch
of your request is executed. It allows you to transform the response data globally or handle errors in a centralized way.
// Add a response interceptor
githubClient.interceptors.response.use(response => {
// Any status code that lie within the range of 2xx cause this function to trigger
console.log('Received successful response!');
// You can transform the response data here if needed
return response;
}, error => {
// Any status codes that falls outside the range of 2xx cause this function to trigger
console.error('An error occurred with the response.');
// You can handle errors globally here
return Promise.reject(error);
});
Interceptors are advanced tools for creating clean, non-repetitive, and maintainable API interaction code.
Activities
Activity 1: Create an API Client
- Create a new file called
apiClient.js
. - Inside this file, use
axios.create()
to make a pre-configured instance for the JSONPlaceholder API (https://jsonplaceholder.typicode.com
). - Export the created instance.
- In your
server.js
, import the client and use it to refactor your/users
route from the first example.
Activity 2: Add a Logger Interceptor
- In
apiClient.js
, add a request interceptor to youraxios
instance. - The interceptor should simply log the method and URL of the outgoing request (e.g.,
Requesting: GET /users
). - Test your
/users
route again and check your console to see the interceptor’s log message.
Knowledge Check
What is the main benefit of using axios.create()
?
- Select an answer to view feedback.
What is a common use case for a request interceptor?
- Select an answer to view feedback.
Summary
In this lesson, you took a deep dive into the advanced features of axios
that make it an industry standard. You learned how to create clean, reusable API clients with axios.create()
and how to globally handle requests and responses with interceptors. These patterns are essential for writing scalable, maintainable code that interacts with external services.
References
- Axios Documentation: Instances
- Axios Documentation: Interceptors
- The Twelve-Factor App: Config - A look ahead at why separating config is important.