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

Walkthrough

Space play step click any line
Three takeaways
  1. 1Clamping and coercing user input keeps pagination math safe from bad query strings.
  2. 2Precomputing a full pagination model on the server keeps templates dumb and declarative.
  3. 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.

Building a paginated orders page in Express — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code