February 10: Pagination in a json-server API with the link header

Background: What’s pagination?

Uh, okay… so?

Starting up json-server

\{^_^}/ hi!Loading db.json
Done
Resources
http://localhost:3000/us-counties
Home
http://localhost:3000
Type s + enter at any time to create a snapshot of the database
{
"us-counties": [
{
"population": 55200,
"state": "Alabama",
"name": "Autauga",
"type": "County"
},
{
"population": 208107,
"state": "Alabama",
"name": "Baldwin",
"type": "County"
},
...

Exploring the link header

<http://localhost:3000/us-counties?state=New%20York&_limit=10&_page=1>; rel="first", <http://localhost:3000/us-counties?state=New%20York&_limit=10&_page=2>; rel="prev", <http://localhost:3000/us-counties?state=New%20York&_limit=10&_page=4>; rel="next", <http://localhost:3000/us-counties?state=New%20York&_limit=10&_page=7>; rel="last"
fetch( currentUrl ).then( response => response.headers.get( "Link" ) ).then(...);

Parsing the link header

  1. The link header only appears on a _limited response with multiple _pages. If you try to call response.headers.get( "Link" ) on a route with only one page, you’ll get a null object.
  2. The link header is a string — not an array or object — and you’ll have to parse it to an Object do anything useful with it.
  • You can use an excellent free package called parse-link-header… but it must be installed separately with npm install parse-link-header, and it doesn’t play nice with a lot of browsers, especially mobile device browsers;
  • You can use a regular expression, like this brain bomb: /^(?:(?:(([^:\/#\?]+:)?(?:(?:\/\/)(?:(?:(?:([^:@\/#\?]+)(?:\:([^:@\/#\?]*))?)@)?(([^:\/#\?\]\[]+|\[[^\/\]@#?]+\])(?:\:([0-9]+))?))?)?)?((?:\/?(?:[^\/\?#]+\/+)*)(?:[^\?#]*)))?(\?[^#]+)?)(#.*)?/… but even if you haven’t read my previous blog posts, you can tell that working with regular expressions is about as fun as flushing your eyes with turpentine;
  • So, for this blog post, to demonstrate and explain, I’ll take the naïve approach and use my trusty set of JavaScipt ginsu knives to .split(), .map(), .replace() and .slice() the link header into a five-star gourmet Object:
function parseLinkHeader( linkHeader ) {
return Object.fromEntries( linkHeader.split( ", " ).map( header => header.split( "; " ) ).map( header => [ header[1].replace( /"/g, "" ).replace( "rel=", "" ), header[0].slice( 1, -1 ) ] ) );
}
function parseLinkHeader( linkHeader ) {
const linkHeadersArray = linkHeader.split( ", " ).map( header => header.split( "; " ) );
const linkHeadersMap = linkHeadersArray.map( header => {
const thisHeaderRel = header[1].replace( /"/g, "" ).replace( "rel=", "" );
const thisHeaderUrl = header[0].slice( 1, -1 );
return [ thisHeaderRel, thisHeaderUrl ]
} );
return Object.fromEntries( linkHeadersMap );
}
  • First let’s whip up a variable, linkHeadersArray, by .split()ting individual routes and rels in the linkHeader by commas, and then splitting each pair of routes and rels by semicolons with .map() to yield the following nested array:
[
[ '<http://localhost:3000/us-counties?state=New%20York&_limit=10&_page=1>', 'rel="first"' ],
[ '<http://localhost:3000/us-counties?state=New%20York&_limit=10&_page=2>', 'rel="prev"' ],
[ '<http://localhost:3000/us-counties?state=New%20York&_limit=10&_page=4>', 'rel="next"' ],
[ '<http://localhost:3000/us-counties?state=New%20York&_limit=10&_page=7>', 'rel="last"' ]
]
  • Next, let’s turn linkHeadersArray into a linkHeadersMap with a .map() function that strips the the carats from the URL with .slice( 1, -1 ), strips everything except the attribute value from the rel with .replace(), and switches their places;
  • Finally, return an Object.fromEntries() of linkHeadersMap that finally gives us a custom-tailored, perfectly-cut Object that looks like this:
{
first: 'http://localhost:3000/us-counties?state=New%20York&_limit=10&_page=1',
prev: 'http://localhost:3000/us-counties?state=New%20York&_limit=10&_page=2',
next: 'http://localhost:3000/us-counties?state=New%20York&_limit=10&_page=4',
last: 'http://localhost:3000/us-counties?state=New%20York&_limit=10&_page=7'
}

Putting the link header to use

let currentUrl = "http://localhost:3000/us-counties?_limit=20&_page=1"function paginate( direction ) {
fetch( currentUrl ).then( response => {
let linkHeaders = parseLinkHeader( response.headers.get( "Link" ) );
if ( !!linkHeaders[ direction ] ) {
currentUrl = linkHeaders[ direction ];
fetchCounties( linkHeaders[ direction ] );
}
} );
}

Conclusion: Here I go, playin’ the star again — there I go, turn the page…

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Josh Frank

Josh Frank

Oh geez, Josh Frank decided to go to Flatiron? He must be insane…