javascript
61 lines · 10 steps
Sharing route logic with router.param in Express
An Express router centralizes article loading and authorization so each route handler stays tiny and focused.
Explained by
highlit
1const express = require('express');
2const createError = require('http-errors');
3const { Article } = require('../models');
4
5const router = express.Router();
6
7router.param('articleId', async (req, res, next, id) => {
8 try {
9 const article = await Article.findByPk(id, {
10 include: ['author', 'tags'],
11 });
12
13 if (!article) {
14 return next(createError(404, 'Article not found'));
15 }
16
17 req.article = article;
18 res.locals.article = article;
19 next();
20 } catch (err) {
21 next(err);
22 }
23});
24
25const requireAuth = (req, res, next) => {
26 if (!req.user) return next(createError(401, 'Authentication required'));
27 next();
28};
29
30const requireOwnership = (req, res, next) => {
31 if (req.article.authorId !== req.user.id) {
32 return next(createError(403, 'You do not own this article'));
33 }
34 next();
35};
36
37const editorChain = [requireAuth, requireOwnership];
38
39router.get('/:articleId', (req, res) => {
40 res.json(req.article.toPublicJSON());
41});
42
43router.patch('/:articleId', editorChain, async (req, res, next) => {
44 try {
45 await req.article.update(req.body, { fields: ['title', 'body', 'status'] });
46 res.json(req.article.toPublicJSON());
47 } catch (err) {
48 next(err);
49 }
50});
51
52router.delete('/:articleId', editorChain, async (req, res, next) => {
53 try {
54 await req.article.destroy();
55 res.status(204).end();
56 } catch (err) {
57 next(err);
58 }
59});
60
61module.exports = router;
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1router.param runs once per matching route to preload and validate a resource before any handler executes.
- 2Composing named middleware into a reusable array keeps authentication and ownership checks consistent across routes.
- 3Passing errors to next() lets a central error handler manage every failure path uniformly.
Related explainers
rust
use serde::Deserialize; #[derive(Debug, Deserialize)] #[serde(untagged)]
Parsing flexible JSON shapes with serde
deserialization
enums
json
Intermediate
6 steps
ruby
require "shellwords" require "open3" module Backup
Building safe shell commands in Ruby
shell-out
subprocess
command-injection
Intermediate
7 steps
python
import time import uuid from django.utils.deprecation import MiddlewareMixin
Attaching per-request context in Django
middleware
request lifecycle
multi-tenancy
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
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/sharing-route-logic-with-router-param-in-express-explained-javascript-e85a/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.