JAVASCRIPT

Fetching All Pages with Cursor-Based API Pagination

Learn to effectively retrieve all data from APIs utilizing cursor-based pagination, fetching successive pages until no more items are available.

/**
 * Fetches all pages from an API endpoint using cursor-based pagination.
 * Assumes the API response structure is { data: [], next_cursor: 'abc' | null }
 *
 * @param {string} baseUrl - The base URL of the API endpoint (e.g., 'https://api.example.com/items').
 * @param {object} initialQueryParams - Initial query parameters (e.g., { limit: 100, status: 'active' }).
 * @returns {Promise<Array<any>>} - A promise that resolves to an array containing all fetched items.
 */
async function fetchAllCursorPaginatedData(baseUrl, initialQueryParams = {}) {
  let allItems = [];
  let nextCursor = null;
  let hasMorePages = true;

  while (hasMorePages) {
    const url = new URL(baseUrl);
    const params = { ...initialQueryParams };

    if (nextCursor) {
      params.cursor = nextCursor; // Add the cursor for subsequent requests
    }

    Object.keys(params).forEach(key => url.searchParams.append(key, params[key]));

    try {
      const response = await fetch(url.toString());
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }
      const result = await response.json();

      if (result && Array.isArray(result.data)) {
        allItems = allItems.concat(result.data);
        nextCursor = result.next_cursor || null; // API might return null or undefined
        hasMorePages = !!nextCursor; // If nextCursor is null/undefined, no more pages
      } else {
        console.warn('API response structure unexpected:', result);
        hasMorePages = false; // Stop if structure is not as expected
      }
    } catch (error) {
      console.error(`Error fetching paginated data from ${url.toString()}:`, error);
      hasMorePages = false; // Stop on error
      throw error; // Re-throw to inform the caller
    }
  }
  return allItems;
}

// Example Usage (assuming a mock API that returns data and a next_cursor):
/*
// Mock API setup for demonstration
let mockData = Array.from({ length: 250 }, (_, i) => ({ id: i + 1, name: `Item ${i + 1}` }));
function mockApiCall(cursor, limit) {
    const start = cursor ? parseInt(cursor) : 0;
    const end = Math.min(start + limit, mockData.length);
    const data = mockData.slice(start, end);
    const newCursor = end < mockData.length ? end.toString() : null;
    return Promise.resolve({ data, next_cursor: newCursor });
}

// Override global fetch for testing (do NOT do this in production)
const originalFetch = global.fetch;
global.fetch = async (input, init) => {
    const urlObj = new URL(input);
    if (urlObj.hostname === 'mockapi.example.com') {
        const cursor = urlObj.searchParams.get('cursor');
        const limit = parseInt(urlObj.searchParams.get('limit') || '50'); // Default limit
        console.log(`Mock API: Fetching with cursor=${cursor}, limit=${limit}`);
        return new Response(JSON.stringify(await mockApiCall(cursor, limit)), {
            status: 200,
            headers: { 'Content-Type': 'application/json' }
        });
    }
    return originalFetch(input, init); // Fallback to actual fetch for other URLs
};


(async () => {
  try {
    console.log('Fetching all items with cursor pagination...');
    const allItems = await fetchAllCursorPaginatedData('https://mockapi.example.com/items', { limit: 50, status: 'active' });
    console.log(`Total items fetched: ${allItems.length}`);
    // console.log('First 5 items:', allItems.slice(0, 5));
    // console.log('Last 5 items:', allItems.slice(-5));
  } catch (error) {
    console.error('Failed to fetch all paginated data:', error);
  } finally {
    global.fetch = originalFetch; // Restore original fetch
  }
})();
*/
How it works: This JavaScript function `fetchAllCursorPaginatedData` demonstrates how to consume an API that uses cursor-based pagination. It iteratively makes `fetch` requests, appending the `next_cursor` (or similar token) obtained from the previous response to the query parameters of the subsequent request. The loop continues until the API indicates there are no more pages (e.g., by returning a `null` or `undefined` cursor). This pattern is crucial for retrieving complete datasets from APIs that don't expose an `offset/limit` or `page` number mechanism.

Need help integrating this into your project?

Our team of expert developers can help you build your custom application from scratch.

Hire DigitalCodeLabs