HTTP and APIs · Lesson 11 of 18
Build a tiny router and middleware chain
Understand what Express does: route matching and a next() middleware chain.
Express removes boilerplate but is not magic. Middleware receives a context and next: it can log, authenticate or parse, then calls next to continue. A router selects a handler by method and path. We will implement the idea without a package.
Request
logger
auth
route handler
Response unwinds
async function run(middleware, ctx) {
async function dispatch(i) {
if (i < middleware.length) await middleware[i](ctx, () => dispatch(i + 1))
}
await dispatch(0)
}
const steps = []
run([
async (_ctx, next) => { steps.push("before"); await next(); steps.push("after") },
async (ctx) => { ctx.body = "ok"; steps.push("handler") }
], {}).then(() => console.log(steps.join(" > ")))Each middleware must either finish the response or call and await next(). Forgetting next stops the chain; calling it twice may run downstream twice. A production wrapper prevents double-next and catches rejected promises.
// Real Express equivalent (Express is not installed in this runner)
import express from "express"
const app = express()
app.use(express.json())
app.use((req, _res, next) => { console.log(req.method, req.url); next() })
app.get("/health", (_req, res) => res.json({ ok: true }))
app.listen(process.env.PORT || 3000)Quick question
A middleware neither responds nor calls next. What happens?
Exercise
Complete middleware adding 1 before next and 10 after it; the handler adds 2. Print 13.
Expected output: 13
async function run(list, ctx) {
async function go(i) { if (i < list.length) await list[i](ctx, () => go(i + 1)) }
await go(0)
}
const ctx = { value: 0 }
run([
async (ctx, next) => {
},
async (ctx) => { ctx.value += 2 }
], ctx).then(() => console.log(ctx.value))Learn more from
Played 0 of 2 games
Sign in to save your progress and earn XP.