Handling API Pagination with Axios and Recursion
Fetching large datasets from paginated REST APIs requires iterating over multiple pages until all records are retrieved. This guide demonstrates how to handle pagination flows by implementing recursive functions using the Axios HTTP client. You will learn how to implement recursion for both page-number and cursor-based pagination strategies, handle termination conditions, and apply best practices such as rate limiting and error handling.
Understanding Recursive Pagination
Recursive pagination works by having an asynchronous function call
itself to request the next page of results as long as a specific
continuation condition is met (e.g., a nextPage index
exists or a nextCursor token is returned). Once the API
indicates that no further pages exist, the function terminates and
returns the accumulated dataset.
1. Page-Number Based Pagination
In page-number or offset-based pagination, the API expects a
page query parameter and typically returns metadata such as
totalPages or the current page index.
const axios = require('axios');
/**
* Recursively fetches all pages from a page-based API endpoint.
*
* @param {string} url - The base API endpoint.
* @param {number} page - The current page to fetch.
* @param {Array} accumulated - The accumulated results array.
* @returns {Promise<Array>} - Resolves to the combined data from all pages.
*/
async function fetchByPage(url, page = 1, accumulated = []) {
try {
const response = await axios.get(url, {
params: { page, limit: 50 }
});
const { data, totalPages } = response.data;
const combinedData = accumulated.concat(data);
// Base condition: Stop if we have reached or exceeded the total pages
if (page >= totalPages || data.length === 0) {
return combinedData;
}
// Recursive call for the next page
return await fetchByPage(url, page + 1, combinedData);
} catch (error) {
console.error(`Failed fetching page ${page}:`, error.message);
throw error;
}
}
// Example usage:
// fetchByPage('https://api.example.com/items').then(all => console.log(all.length));2. Cursor-Based Pagination
Cursor-based pagination relies on an opaque pointer (such as an ID or a token) returned in the response payload or headers to fetch the subsequent page.
const axios = require('axios');
/**
* Recursively fetches all pages from a cursor-based API endpoint.
*
* @param {string} url - The base API endpoint.
* @param {string|null} cursor - The cursor token for the next page.
* @param {Array} accumulated - The accumulated results array.
* @returns {Promise<Array>} - Resolves to the combined dataset.
*/
async function fetchByCursor(url, cursor = null, accumulated = []) {
try {
const response = await axios.get(url, {
params: {
limit: 100,
...(cursor && { cursor })
}
});
const { items, nextCursor } = response.data;
const combinedData = accumulated.concat(items);
// Base condition: Stop if there is no next cursor or items array is empty
if (!nextCursor || items.length === 0) {
return combinedData;
}
// Recursive call with the next cursor token
return await fetchByCursor(url, nextCursor, combinedData);
} catch (error) {
console.error('Cursor pagination failed:', error.message);
throw error;
}
}Key Considerations and Best Practices
1. Define Clear Base Conditions
Always verify that a stopping condition exists to prevent infinite loops:
- Verify whether the current page index is equal to or exceeds
totalPages. - Check whether the returned data array is empty.
- Check whether the
nextCursorornext_page_urlproperty isnullorundefined.
2. Handle Rate Limits
Rapid consecutive requests can trigger HTTP
429 Too Many Requests responses. Insert a delay between
calls if the target API imposes strict rate limits:
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
async function fetchWithThrottle(url, page = 1, accumulated = []) {
const response = await axios.get(url, { params: { page } });
const { data, hasMore } = response.data;
const combined = accumulated.concat(data);
if (!hasMore) return combined;
await delay(200); // 200ms delay between requests
return fetchWithThrottle(url, page + 1, combined);
}3. Manage Memory for Large Datasets
Accumulating hundreds of thousands of items in an in-memory array can cause memory exhaustion. For massive datasets, consider processing or streaming each batch to a database or file inside the recursive function rather than concatenating everything into memory before returning.