JAVASCRIPT

Parse HTTP Link Headers for API Pagination & Navigation

Extract and interpret navigation links (next, prev, first, last) from an API's HTTP 'Link' header using JavaScript, enabling HATEOAS-driven pagination.

function parseLinkHeader(linkHeader) {
  const links = {};
  if (linkHeader) {
    linkHeader.split(',').forEach(linkPart => {
      const parts = linkPart.split(';');
      const url = parts[0].replace(/<(.*)>/, '$1').trim();
      const rel = parts[1].replace(/rel="(.*)"/, '$1').trim();
      links[rel] = url;
    });
  }
  return links;
}

// Example Usage:
async function fetchDataWithLinkHeader(url, customFetch = fetch) {
  try {
    const response = await customFetch(url);

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    const linkHeader = response.headers.get('Link');
    const paginationLinks = parseLinkHeader(linkHeader);

    const data = await response.json();

    console.log('Fetched Data:', data);
    console.log('Parsed Link Header:', paginationLinks);

    // Example of using pagination links:
    if (paginationLinks.next) {
      console.log('Next page URL:', paginationLinks.next);
      // You could then fetch the next page:
      // fetchDataWithLinkHeader(paginationLinks.next);
    }
    if (paginationLinks.last) {
        console.log('Last page URL:', paginationLinks.last);
    }

    return { data, paginationLinks };

  } catch (error) {
    console.error('Error fetching data:', error);
    throw error;
  }
}

// Mock API response with a Link header for demonstration
const mockApiFetchWithLinks = async (url) => {
    console.log(`Mock Fetching: ${url}`);
    const pageMatch = url.match(/page=(\d+)/);
    const currentPage = pageMatch ? parseInt(pageMatch[1]) : 1;
    const totalPages = 5;

    let linkHeader = '';
    if (currentPage < totalPages) {
        linkHeader += `<https://api.example.com/items?page=${currentPage + 1}&per_page=10>; rel="next"`;
    }
    if (currentPage > 1) {
        if (linkHeader) linkHeader += ', ';
        linkHeader += `<https://api.example.com/items?page=${currentPage - 1}&per_page=10>; rel="prev"`;
    }
    if (linkHeader) linkHeader += ', ';
    linkHeader += `<https://api.example.com/items?page=1&per_page=10>; rel="first", `; // Always show first
    linkHeader += `<https://api.example.com/items?page=${totalPages}&per_page=10>; rel="last"`; // Always show last

    return {
        ok: true,
        status: 200,
        headers: {
            get: (headerName) => (headerName === 'Link' ? linkHeader : null),
        },
        json: () => Promise.resolve({
            page: currentPage,
            per_page: 10,
            total_pages: totalPages,
            items: Array.from({ length: 10 }, (_, i) => `Item ${((currentPage - 1) * 10) + i + 1}`)
        }),
    };
};

// Example 1: Fetching the first page
fetchDataWithLinkHeader('https://api.example.com/items?page=1&per_page=10', mockApiFetchWithLinks)
    .then(({ data, paginationLinks }) => {
        console.log('--- Page 1 ---');
        console.log('Data (first 2 items):', data.items.slice(0,2), '...');
        console.log('Links:', paginationLinks);
    })
    .then(() => {
        // Example 2: Fetching a middle page
        console.log('
--- Fetching Page 3 ---');
        return fetchDataWithLinkHeader('https://api.example.com/items?page=3&per_page=10', mockApiFetchWithLinks);
    })
    .then(({ data, paginationLinks }) => {
        console.log('Data (first 2 items):', data.items.slice(0,2), '...');
        console.log('Links:', paginationLinks);
    })
    .catch(error => console.error('Overall Error:', error));
How it works: Many RESTful APIs use the HTTP `Link` header to provide discoverable navigation links (e.g., 'next', 'prev', 'first', 'last') for pagination and HATEOAS. This JavaScript snippet provides a utility function, `parseLinkHeader`, to parse this header string into a more usable JavaScript object. It helps frontend applications dynamically build pagination controls without hardcoding URL structures, making them more resilient to API changes and promoting a more truly RESTful client implementation.

Need help integrating this into your project?

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

Hire DigitalCodeLabs