Mastering Axios: A Technical Guide to Building Your Food Delivery App🍔✨
Ankita Kanchan
Posted on September 1, 2024
In today’s world, efficient and reliable HTTP requests are the backbone of any web application, especially for something as critical as a food delivery app. In this guide, we'll explore how to use Axios to build and enhance a food delivery app by making seamless API requests, handling responses, managing errors, and much more.
1. Introduction to Axios
Axios is a promise-based HTTP client that is widely used for making HTTP requests in JavaScript applications. It works seamlessly in both the browser and Node.js environments. Here's why Axios is often chosen over other HTTP clients:
Why Choose Axios?
- Promise-Based Interface: Axios leverages promises, making it easy to work with modern async/await syntax, leading to cleaner and more readable code.
- Request and Response Interceptors: Provides hooks to run custom code or modify requests/responses globally.
- Automatic JSON Data Handling: No need to manually parse or stringify JSON data—Axios does it for you.
- Broad Browser Support: Axios is supported across all modern browsers and can be used in Node.js.
Installing Axios
To get started, you need to install Axios in your project:
npm install axios
Or if you prefer using yarn:
yarn add axios
After installation, you can import Axios in your JavaScript files:
import axios from 'axios';
With Axios installed and ready, let's move on to the core functionalities.
2. Making HTTP Requests
In a food delivery app, interacting with various APIs is crucial. Whether you’re fetching a list of restaurants, placing an order, or tracking delivery, Axios makes this easy.
Fetching Restaurant Data (GET Request)
Let’s start by fetching a list of restaurants. Here’s how you can make a simple GET request:
axios.get('https://api.foodapp.com/restaurants')
.then(response => {
console.log('List of Restaurants:', response.data);
// Further processing or updating the UI
})
.catch(error => {
console.error('Error fetching restaurant data:', error);
});
In this example, Axios sends a GET request to the API endpoint to retrieve restaurant data. The then
block handles the successful response, while the catch
block captures and logs any errors that occur.
Placing an Order (POST Request)
When a user decides to place an order, you’ll need to send that order data to the server. Here’s how:
axios.post('https://api.foodapp.com/orders', {
restaurantId: 42,
items: [
{ id: 101, name: 'Margherita Pizza', quantity: 1 },
{ id: 202, name: 'Caesar Salad', quantity: 2 }
],
userId: 301,
deliveryAddress: '123 Main Street'
})
.then(response => {
console.log('Order placed successfully:', response.data);
// Redirect user to order confirmation page
})
.catch(error => {
console.error('Error placing order:', error);
});
Here, we’re sending a POST request with the order details in the request body. This information is typically sent as JSON. The server processes the order and returns a confirmation in the response, which you can then use to update the UI.
Updating Order Status (PUT Request)
If you need to update an existing order, such as changing the delivery address or order items, you can use a PUT request:
axios.put('https://api.foodapp.com/orders/123', {
deliveryAddress: '456 New Street'
})
.then(response => {
console.log('Order updated successfully:', response.data);
})
.catch(error => {
console.error('Error updating order:', error);
});
PUT requests are used to update resources on the server. In this case, the delivery address of an order is being updated.
3. Request Configuration: Customizing Your Requests
Axios allows you to configure your requests globally or per-request, giving you flexibility in how you interact with APIs.
Global Configuration
Instead of setting the same base URL or headers in every request, you can define them globally:
axios.defaults.baseURL = 'https://api.foodapp.com';
axios.defaults.headers.common['Authorization'] = 'Bearer YOUR_TOKEN_HERE';
axios.defaults.timeout = 5000; // Set a global timeout of 5 seconds
This configuration is applied to all requests, saving you from repetition and ensuring consistency across your app.
Per-Request Configuration
Sometimes, you might need to configure specific requests differently. For instance, you might want to override the global timeout for a particular request:
axios.get('/restaurants', {
timeout: 10000 // Override global timeout for this request
})
.then(response => console.log('Restaurant data:', response.data))
.catch(error => console.error('Error fetching data:', error));
This flexibility allows you to tailor each request to the specific needs of your app.
4. Handling Responses: Processing Data Efficiently
After making a request, the response from the server needs to be handled appropriately. Axios provides a clean way to access not just the data, but also additional response details.
Inspecting the Response Object
The response object from Axios includes more than just data; it provides the status, headers, and more:
axios.get('/restaurants')
.then(response => {
console.log('Status:', response.status); // HTTP status code
console.log('Headers:', response.headers); // HTTP headers
console.log('Data:', response.data); // Response data (restaurants)
})
.catch(error => {
console.error('Error fetching data:', error);
});
This information can be crucial for tasks such as handling different HTTP statuses or logging response times.
Transforming Response Data
Sometimes, the data you receive isn’t in the exact format you need. Axios allows you to transform this data easily:
axios.get('/restaurants')
.then(response => {
const formattedData = response.data.map(restaurant => ({
name: restaurant.name.toUpperCase(),
cuisine: restaurant.cuisine,
rating: `${restaurant.rating} stars`
}));
console.log('Formatted Restaurants:', formattedData);
})
.catch(error => {
console.error('Error fetching or transforming data:', error);
});
This transformation ensures that the data is in the most useful format for your application, reducing the need for additional processing later on.
5. Error Handling: Managing Failures Gracefully
Errors are inevitable, especially when dealing with network requests. Axios provides a robust mechanism for catching and handling these errors.
Basic Error Handling
Here’s how you can catch and handle errors in your Axios requests:
axios.get('/restaurants')
.catch(error => {
if (error.response) {
console.error('Server responded with an error:', error.response.status);
} else if (error.request) {
console.error('No response received:', error.request);
} else {
console.error('Error setting up request:', error.message);
}
});
This code snippet handles different types of errors:
- Response errors occur when the server responds with a status outside the 2xx range.
- Request errors happen when the request is made but no response is received (e.g., network issues).
- Setup errors occur before the request is even made (e.g., a misconfigured request).
Retrying Requests
Sometimes, you may want to automatically retry a failed request, especially for transient errors like network hiccups:
function retryRequest(config, retries = 3) {
return axios(config).catch(error => {
if (retries > 0) {
console.log(`Retrying request... (${retries} retries left)`);
return retryRequest(config, retries - 1);
} else {
return Promise.reject(error);
}
});
}
retryRequest({ url: '/restaurants' })
.then(response => console.log('Data:', response.data))
.catch(error => console.error('Final error after retries:', error));
This recursive function retries the request a specified number of times before finally throwing an error. It’s particularly useful for scenarios where temporary network issues are common.
6. Interceptors: Advanced Request and Response Handling
Interceptors allow you to run your code or modify requests and responses before they are handled by then
or catch
.
Request Interceptors
Let’s say you want to attach an authentication token to every request:
axios.interceptors.request.use(config => {
config.headers['Authorization'] = 'Bearer YOUR_TOKEN_HERE';
console.log('Request made with:', config);
return config;
}, error => {
return Promise.reject(error);
});
This interceptor automatically adds the authorization header to every outgoing request, ensuring that the user is authenticated for all API calls.
Response Interceptors
You can also use interceptors to handle responses globally:
axios.interceptors.response.use(response => {
console.log('Response received:', response);
return response;
}, error => {
if (error.response.status === 401) {
console.error('Unauthorized access - redirecting to login.');
// Redirect to login or refresh the token
}
return Promise.reject(error);
});
This response interceptor checks for specific status codes, such as 401 Unauthorized
, and allows you to take action,
such as redirecting to a login page.
7. Cancelling Requests: Handling User Navigation and Timeouts
In some cases, you might want to cancel an HTTP request, for example, if the user navigates away from the page before the request completes.
Cancelling an Axios Request
Here’s how you can cancel a request using Axios:
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
axios.get('/restaurants', {
cancelToken: source.token
})
.then(response => {
console.log('Restaurants data:', response.data);
})
.catch(thrown => {
if (axios.isCancel(thrown)) {
console.log('Request canceled:', thrown.message);
} else {
console.error('Request failed:', thrown);
}
});
// Later, cancel the request
source.cancel('Operation canceled by the user.');
By using a cancel token, you can easily cancel the request if the user navigates away or if you determine that the request is no longer necessary.
8. Concurrent Requests: Efficiently Managing Multiple API Calls
In a food delivery app, you might need to fetch data from multiple endpoints simultaneously, such as restaurant details and current promotions.
Handling Multiple Requests
Axios makes it simple to perform concurrent requests using axios.all
:
axios.all([
axios.get('/restaurants/42'),
axios.get('/promotions/current')
])
.then(axios.spread((restaurantResponse, promotionsResponse) => {
console.log('Restaurant:', restaurantResponse.data);
console.log('Promotions:', promotionsResponse.data);
}))
.catch(error => {
console.error('Error with concurrent requests:', error);
});
In this example, two requests are made simultaneously, and their results are handled together, improving the efficiency of your application by reducing the overall loading time.
9. Axios and Authentication: Securing Your API Interactions
Authentication is a critical aspect of any application that handles sensitive data, such as user profiles and payment information.
Handling Login Requests
Here’s how you can manage user login and store the authentication token:
axios.post('/auth/login', {
username: 'john_doe',
password: 'securepassword123'
})
.then(response => {
const token = response.data.token;
axios.defaults.headers.common['Authorization'] = `Bearer ${token}`;
console.log('User logged in successfully.');
})
.catch(error => {
console.error('Login failed:', error);
});
After a successful login, the token is stored and attached to all subsequent requests, ensuring that the user remains authenticated throughout their session.
Refreshing Tokens
For long-running sessions, you might need to refresh the authentication token. Here’s a simple way to handle token refresh:
axios.interceptors.response.use(response => {
return response;
}, error => {
const originalRequest = error.config;
if (error.response.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
return axios.post('/auth/refresh-token')
.then(res => {
if (res.status === 200) {
axios.defaults.headers.common['Authorization'] = `Bearer ${res.data.token}`;
originalRequest.headers['Authorization'] = `Bearer ${res.data.token}`;
return axios(originalRequest);
}
});
}
return Promise.reject(error);
});
This interceptor checks for a 401 Unauthorized
response and attempts to refresh the token before retrying the original request.
10. Customizing Axios: Tailoring Axios to Fit Your Application
In complex applications, you might need to create multiple Axios instances with different configurations.
Creating a Custom Axios Instance
For example, you might want a separate instance for making API calls related to user accounts:
const userApi = axios.create({
baseURL: 'https://api.foodapp.com/users',
timeout: 3000,
headers: {'X-Custom-Header': 'userAPI'}
});
userApi.get('/profile')
.then(response => console.log('User profile:', response.data))
.catch(error => console.error('Error fetching user profile:', error));
This instance is configured with a specific base URL and timeout, making it easier to manage different types of requests in your app.
11. Using Axios with Async/Await: Simplifying Your Asynchronous Code
Async/await syntax is a powerful feature in JavaScript that simplifies working with asynchronous code, making it more readable and maintainable.
Fetching Data with Async/Await
Here’s how you can use async/await with Axios to fetch restaurant data:
async function fetchRestaurantData() {
try {
const response = await axios.get('/restaurants');
console.log('Restaurant data:', response.data);
} catch (error) {
console.error('Error fetching restaurant data:', error);
}
}
fetchRestaurantData();
This approach allows you to write asynchronous code that looks synchronous, making it easier to understand and debug.
12. Working with Cookies: Maintaining Session State
Cookies are often used to maintain session state, particularly in web applications that require user authentication.
Sending Cookies with Requests
If your backend uses cookies to manage sessions, you can ensure they’re sent with every request:
axios.defaults.withCredentials = true;
axios.get('/user/profile')
.then(response => console.log('User profile:', response.data))
.catch(error => console.error('Error fetching profile:', error));
By setting withCredentials
to true
, Axios will include cookies in cross-site requests, enabling session management.
13. Uploading and Downloading Files: Handling Media with Axios
In a food delivery app, you might need to upload images (e.g., restaurant logos) or download files (e.g., PDF menus).
Uploading an Image
Here’s how to upload an image file to the server:
const formData = new FormData();
formData.append('image', fileInput.files[0]);
axios.post('/restaurants/42/logo', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
})
.then(response => console.log('Image uploaded successfully:', response.data))
.catch(error => console.error('Error uploading image:', error));
This code uses FormData
to send a file along with the request, ensuring it’s handled correctly by the server.
Downloading a File
And here’s how to download a file, such as a PDF menu:
axios({
url: '/restaurants/42/menu.pdf',
method: 'GET',
responseType: 'blob', // Ensures the response is treated as binary data
})
.then(response => {
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', 'menu.pdf');
document.body.appendChild(link);
link.click();
})
.catch(error => console.error('Error downloading file:', error));
This method downloads the file and triggers a download in the user’s browser.
14. Axios Adapters: Extending Axios for Custom Use Cases
Axios adapters allow you to extend or modify how Axios handles requests, which can be useful in specialized scenarios.
Creating a Custom Adapter
Let’s say you want to log every request to a database for analytics purposes:
function loggingAdapter(config) {
// Log request to database
logRequestToDatabase(config);
// Proceed with the default request handling
return axios.defaults.adapter(config);
}
const customAxios = axios.create({
adapter: loggingAdapter
});
customAxios.get('/restaurants')
.then(response => console.log('Restaurant data:', response.data))
.catch(error => console.error('Error fetching data:', error));
This adapter logs each request before proceeding with the default handling, allowing you to extend Axios’s behavior without modifying its core functionality.
15. Debugging and Logging: Keeping Your App Running Smoothly
Effective logging and debugging are essential for maintaining a robust application, especially when dealing with complex API interactions.
Logging Requests and Responses
Here’s how you can log all outgoing requests and incoming responses:
axios.interceptors.request.use(request => {
console.log('Starting Request', request);
return request;
});
axios.interceptors.response.use(response => {
console.log('Response:', response);
return response;
});
This setup logs the full details of every request and response, providing valuable insights during development and debugging.
Handling Errors in Debug Mode
For development, you might want to log additional details about errors:
axios.interceptors.response.use(
response => response,
error => {
if (process.env.NODE_ENV === 'development') {
console.error('Error response:', error.response);
}
return Promise.reject(error);
}
);
This interceptor logs error details only in development mode, helping you diagnose issues without cluttering your production logs.
Conclusion
Axios is a powerful tool that can significantly streamline your HTTP requests, making your food delivery app more efficient, reliable, and easier to maintain. Whether you’re handling authentication, uploading files, or dealing with concurrent requests, Axios provides the flexibility and control needed to manage these tasks effectively.
With the concepts and examples covered in this guide, you’re now equipped to build and enhance your food delivery app, ensuring it’s robust and ready to handle real-world demands.
Happy coding🎉!
Posted on September 1, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 9, 2024
October 2, 2024