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
java
public final class ShippingAddress { private final String recipient; private final String line1; private final String line2;
Building immutable objects with the Builder pattern
builder-pattern
immutability
validation
Intermediate
7 steps
php
final class UserActivityStream { public function __construct( private readonly \PDO $db,
Streaming user activity with PHP generators
generators
pagination
lazy-evaluation
Intermediate
8 steps
rust
use axum::{ extract::Path, http::{header::ACCEPT, HeaderMap, StatusCode}, response::{Html, IntoResponse, Json, Response},
Content negotiation in an Axum handler
content-negotiation
http-headers
serialization
Intermediate
8 steps
php
<?php namespace App\Casts;
How a custom Eloquent cast wraps an Address in Laravel
value-objects
serialization
data-mapping
Intermediate
7 steps
javascript
import { useSearchParams } from "react-router-dom"; const TABS = [ { id: "overview", label: "Overview" },
URL-driven tabs with React Router
url-state
query-params
accessibility
Intermediate
8 steps
python
import re from email_validator import validate_email, EmailNotValidError _DISPOSABLE_DOMAINS = {
Normalizing signup emails in Python
validation
normalization
data-cleaning
Intermediate
8 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.