javascript
44 lines · 7 steps
Building a paginated orders page in Express
An Express route reads a page number, queries a slice of orders, and renders a full pagination model for the view.
Explained by
highlit
1const express = require('express');
2const router = express.Router();
3const db = require('../db');
4
5const PER_PAGE = 20;
6
7router.get('/orders', async (req, res, next) => {
8 try {
9 const page = Math.max(1, parseInt(req.query.page, 10) || 1);
10 const offset = (page - 1) * PER_PAGE;
11
12 const [{ count }] = await db('orders').count({ count: '*' });
13 const totalPages = Math.max(1, Math.ceil(count / PER_PAGE));
14
15 const orders = await db('orders')
16 .select('orders.id', 'orders.total', 'orders.status', 'orders.created_at', 'customers.name as customer')
17 .join('customers', 'customers.id', 'orders.customer_id')
18 .orderBy('orders.created_at', 'desc')
19 .limit(PER_PAGE)
20 .offset(offset);
21
22 res.render('orders/index', {
23 title: 'Orders',
24 orders,
25 pagination: {
26 page,
27 totalPages,
28 hasPrev: page > 1,
29 hasNext: page < totalPages,
30 prevUrl: `/orders?page=${page - 1}`,
31 nextUrl: `/orders?page=${page + 1}`,
32 pages: Array.from({ length: totalPages }, (_, i) => ({
33 number: i + 1,
34 url: `/orders?page=${i + 1}`,
35 current: i + 1 === page,
36 })),
37 },
38 });
39 } catch (err) {
40 next(err);
41 }
42});
43
44module.exports = router;
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Clamping and coercing user input keeps pagination math safe from bad query strings.
- 2Precomputing a full pagination model on the server keeps templates dumb and declarative.
- 3Wrapping async handlers in try/catch and forwarding to next() routes errors through Express middleware.
Related explainers
javascript
const { Pool } = require('pg'); const pool = new Pool({ connectionString: process.env.DATABASE_URL,
Per-request Postgres connections in Express
connection-pooling
middleware
transactions
Intermediate
8 steps
ruby
class Document < ApplicationRecord class StaleObjectError < StandardError def initialize(id) super("Document ##{id} was modified by another process")
Optimistic locking with retries in Rails
optimistic-locking
concurrency
transactions
Advanced
8 steps
javascript
import { Suspense } from 'react'; import { searchProducts } from '@/lib/products'; import SearchInput from './search-input';
Streaming search results in a Next.js Server Component
server-components
suspense
streaming
Intermediate
8 steps
javascript
import { useState } from 'react'; function StarRating({ value = 0, max = 5, onChange, size = 24 }) { const [hovered, setHovered] = useState(null);
Building an accessible star rating in React
controlled-component
accessibility
state-management
Intermediate
8 steps
javascript
import { notFound } from 'next/navigation' import { Suspense } from 'react' import { getPostBySlug, getRelatedPosts } from '@/lib/posts' import { RelatedPosts } from '@/components/related-posts'
Building a dynamic blog post page in Next.js
server-components
dynamic-routing
metadata
Intermediate
8 steps
go
type CreateUserInput struct { Name string `json:"name" binding:"required,min=2,max=64"` Email string `json:"email" binding:"required,email"` Password string `json:"password" binding:"required,min=8"`
Turning Gin validation errors into JSON
validation
request-binding
error-handling
Intermediate
9 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-a-paginated-orders-page-in-express-explained-javascript-41fc/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.