Streamlining API Calls: The Power of Axios Instances and Middleware
In the AplicacionJoyeria project, ensuring robust and consistent interaction with our backend APIs is paramount. We recently implemented a significant improvement in how we manage these interactions, focusing on maintainability and error handling.
The Situation
Before this update, our application's API calls might have been scattered, leading to repetitive code for common tasks such as attaching authorization tokens, setting default headers, or handling global error states. This decentralized approach made it challenging to apply consistent logic across all API requests, leading to increased development time for new features and more complex debugging when issues arose.
The Solution: Centralized API Calls with Axios Middleware
To address these challenges, we introduced a dedicated axios instance configured with request and response interceptors. This pattern effectively creates a middleware layer for all our API communications, centralizing common functionalities.
An axios instance allows us to define a base URL and default headers once, which are then applied to all requests made through that instance. The true power comes from interceptors:
- Request Interceptors: These functions run before a request is sent. They are ideal for tasks like adding dynamic authorization headers, logging request details, or modifying request parameters.
- Response Interceptors: These functions run after a response (or error) is received but before it's passed back to the calling code. They are perfect for normalizing response data, automatically refreshing authentication tokens, or handling global error codes (e.g., redirecting users on a 401 Unauthorized status).
Here's an illustrative example of how such an instance and its interceptors might be configured:
import axios from 'axios';
// Create a custom Axios instance
const apiClient = axios.create({
baseURL: 'https://api.example.com/v1',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
timeout: 10000 // 10 seconds timeout
});
// Add a request interceptor
apiClient.interceptors.request.use(
(config) => {
// For example, attach an authorization token from local storage
const authToken = localStorage.getItem('jwtToken');
if (authToken) {
config.headers.Authorization = `Bearer ${authToken}`;
}
console.log('Request sent:', config.url);
return config;
},
(error) => {
// Handle request error
return Promise.reject(error);
}
);
// Add a response interceptor
apiClient.interceptors.response.use(
(response) => {
// Any successful response can be processed here
console.log('Response received:', response.config.url);
return response;
},
async (error) => {
const originalRequest = error.config;
// Example: Handle 401 Unauthorized errors
if (error.response.status === 401 && !originalRequest._retry) {
originalRequest._retry = true; // Prevent infinite retry loops
try {
// Logic to refresh token or re-authenticate
// For now, just log and reject
console.error('Unauthorized: User needs to re-authenticate.');
// Optionally, redirect to login page
// window.location.href = '/login';
// return await apiClient(originalRequest); // Retry the original request with new token
} catch (refreshError) {
// Handle token refresh failure
return Promise.reject(refreshError);
}
}
console.error('Response error:', error.response?.status, error.message);
return Promise.reject(error);
}
);
export default apiClient;
// Usage example:
// apiClient.get('/products').then(response => console.log(response.data));
The Technical Lesson
By leveraging custom axios instances and their interceptors, we achieve a more modular, maintainable, and robust networking layer. This pattern eliminates repetitive code, ensures consistency in request headers and error handling, and significantly simplifies future updates to our API communication strategy. It's a clear demonstration of the DRY (Don't Repeat Yourself) principle in action, making our codebase cleaner and more scalable.
The Takeaway
Centralize your API communication logic using dedicated axios instances and carefully configured request and response interceptors. This practice will dramatically improve the maintainability, consistency, and error handling capabilities of your application's network requests, leading to a more stable and easier-to-debug system.
Generated with Gitvlg.com