Express 5 Changes

Express 5 and What Changed from Express 4

Express 5.0.0 24,430 shipped on 10 September 2024, a decade after the branch opened, and 5.1.0 took the latest tag on npm 2,036 in March 2025. The 4.x line still ships under latest-4 (4.22.3) with security fixes, but new work starts on 5. Most Express 4 code runs unchanged; the breaks are in path syntax and old signatures.

Express 4 idioms in older tutorials, and their Express 5 replacements
Express 4 Express 5 Why
app.get('/files/*', h) app.get('/files/*splat', h) Wildcards must be named
app.get('/:file.:ext?', h) app.get('/:file{.:ext}', h) Optional parts use braces
npm i body-parser express.json() built in Parsers moved into core
res.json(obj, 201) res.status(201).json(obj) Two-argument forms removed
.catch(next) required rejections auto-forwarded Async handlers just work

Two more: req.query is now a read-only getter, so code that stashed a normalized value onto it throws, and req.body is undefined rather than {} when no parser ran. The path syntax bites first, though, because path-to-regexp 8 refuses ambiguous patterns at startup: a bare '/files/*' throws Missing parameter name at index 8, and '/books/:id?' throws Unexpected ? at index 10. A named wildcard also behaves differently — req.params.splat is an array of segments, so /files/covers/2026/ab.png gives {"splat":["covers","2026","ab.png"]}. The Path Syntax of Express 5 has the full syntax.

The other change to internalize is automatic rejection forwarding. In Express 4 an async handler that threw left the request hanging until the socket timed out, because the router never saw the rejection. Express 5 awaits the handler's return value and passes a rejection to next, so the error thrown by app.get('/boom', async () => { throw new Error('unreachable'); }) lands in the error middleware as a 500.