javascript
41 lines · 8 steps
Building and parsing pagination URLs
A round-trip pair of functions that serialize list state into a query string and read it back out safely.
Explained by
highlit
1export function buildPaginationUrl(baseUrl, { page, perPage, filters = {}, sort } = {}) {
2 const url = new URL(baseUrl);
3 const params = url.searchParams;
4
5 if (page != null) params.set('page', String(page));
6 if (perPage != null) params.set('per_page', String(perPage));
7 if (sort) params.set('sort', sort);
8
9 for (const [key, value] of Object.entries(filters)) {
10 params.delete(key);
11 if (Array.isArray(value)) {
12 for (const item of value) params.append(key, item);
13 } else if (value != null && value !== '') {
14 params.set(key, String(value));
15 }
16 }
17
18 params.sort();
19 return url.toString();
20}
21
22export function parseListParams(queryString) {
23 const params = new URLSearchParams(queryString);
24 const page = Number.parseInt(params.get('page') ?? '1', 10);
25 const perPage = Number.parseInt(params.get('per_page') ?? '25', 10);
26
27 const reserved = new Set(['page', 'per_page', 'sort']);
28 const filters = {};
29 for (const key of new Set(params.keys())) {
30 if (reserved.has(key)) continue;
31 const values = params.getAll(key);
32 filters[key] = values.length > 1 ? values : values[0];
33 }
34
35 return {
36 page: Number.isNaN(page) ? 1 : Math.max(page, 1),
37 perPage: Number.isNaN(perPage) ? 25 : Math.min(perPage, 100),
38 sort: params.get('sort'),
39 filters,
40 };
41}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1The URL and URLSearchParams APIs handle escaping and multi-value keys so you never hand-concatenate query strings.
- 2Sorting params before stringifying makes otherwise-equivalent URLs byte-identical, which is friendly to caches.
- 3Parsing untrusted input should always clamp and default values rather than trust whatever arrived.
Related explainers
php
<?php namespace App\Services\Checkout;
Validating coupons with Laravel's Pipeline
pipeline
chain of responsibility
transactions
Intermediate
7 steps
javascript
function evaluate(expression) { const tokens = tokenize(expression); let pos = 0;
Building a recursive descent calculator
parsing
recursion
operator-precedence
Intermediate
8 steps
php
<?php namespace App\Services;
How a password strength validator works in PHP
validation
regular-expressions
data-driven
Intermediate
8 steps
rust
use axum::{ extract::{Path, State}, response::sse::{Event, KeepAlive, Sse}, };
Streaming import progress with SSE in Axum
server-sent-events
streams
watch-channel
Advanced
7 steps
javascript
import { useState, useEffect, useCallback, useRef } from 'react'; const cache = new Map(); const inflight = new Map();
Building a stale-while-revalidate hook in React
caching
request-deduplication
custom-hooks
Advanced
10 steps
rust
use chrono::{Duration, NaiveDate}; #[derive(Debug)] pub struct DateRange {
Parsing and iterating date ranges in Rust
error-handling
iterators
parsing
Intermediate
7 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/building-and-parsing-pagination-urls-explained-javascript-b8f1/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.