Express Knowledge Patch
Use this skill when upgrading an Express application, reviewing route syntax, debugging request or response behavior, or adopting current file-response and handler features.
Prefer the application's manifest, code, and tests when they disagree with general guidance. Check the installed Express version before applying behavior that changed in a patch release.
Reference Index
| Reference | Topics |
|---|---|
| Migration, routing, and handlers | Runtime floor, codemods, route-path grammar, rejected promises, native promises, and app.router |
| Requests, middleware, and rendering | Parser defaults, request properties, dot-directories, Brotli requests, and asynchronous views |
| Responses and files | MIME mappings, stricter response APIs, binary bodies, file ETags, links, and redirects |
| Operations and release behavior | Listen failures, debug namespaces, npm lifecycle policy, and the query-parser reversion |
Migration Priorities
Start with the changes most likely to prevent an application from starting or matching requests:
- Run on Node.js 18 or newer.
- Install the desired Express major explicitly during migration.
- Run the official upgrade codemod, then review its changes.
- Rewrite route strings that use unnamed wildcards, question-mark optionals, or regexp characters.
- Recheck parser assumptions, writable request properties, and static dot-directory access.
- Exercise asynchronous failure paths and server-startup failures.
Install and run all available migrations:
npm install "express@5"
npx @expressjs/codemod upgrade
Run one named transform when a focused migration is preferable:
npx @expressjs/codemod name-of-the-codemod
Codemods handle mechanical changes. Keep integration tests around routes, middleware ordering, parsing, static assets, errors, and startup because those areas also contain behavior changes.
Route-Path Grammar
Name every wildcard. Use /*splat when the root path must not match, and use
/{*splat} when it must:
app.get('/{*splat}', handler)
Replace ? optionals with braces:
app.get('/:file{.:ext}', handler)
String paths no longer accept regexp characters. Use an array for explicit alternatives:
app.get(['/discussion/:slug', '/page/:slug'], handler)
The characters ()[]?+! are reserved in string paths and must be escaped when
they are literal. Parameter names must be valid JavaScript identifiers or
quoted names.
Asynchronous Error Handling
Returned rejected promises reach error middleware as if the handler had called
next(err). An async handler can therefore throw or let an awaited promise
reject:
app.get('/user/:id', async (req, res) => {
const user = await loadUser(req.params.id)
res.json(user)
})
app.use((err, req, res, next) => {
res.status(500).send(err.message)
})
Return native promises from router handlers. Non-native promise-like return
values are deprecated. Prefer async handlers or an actual Promise.
Parser and Request Defaults
Audit code that depended on older parser behavior:
express.urlencoded()defaultsextendedtofalse.- The default query parser is
"simple". req.queryis a getter rather than a writable property.- Without body-parsing middleware,
req.bodyisundefined. req.hostretains the port number.
Do not infer complex query parsing from the presence of nested-looking query keys. Configure the intended parser behavior explicitly when the application depends on it.
Static Dot-Directories
express.static() defaults dotfiles to "ignore". A URL under a directory
such as /.well-known therefore returns 404 unless it is intentionally
allowed.
Mount only the required dot-directory before the general static middleware:
app.use(
'/.well-known',
express.static('public/.well-known', { dotfiles: 'allow' })
)
app.use(express.static('public'))
Avoid allowing all dotfiles merely to expose one public protocol directory.
Startup Errors
app.listen() reports server error events to its callback. Treat a callback
argument as failure and use the server only after that check:
const server = app.listen(8080, '0.0.0.0', (error) => {
if (error) throw error
console.log(server.address())
})
Test port conflicts or other bind failures so deployment code does not mistake a failed listener for a successful startup.
Response Tightening
Review response calls for stricter behavior:
res.clearCookie()ignores suppliedmaxAgeandexpiresoptions.res.status()accepts only integer status codes from 100 through 999.res.vary()throws when its field argument is missing.res.redirect()warns when its status or URL argument isundefined.
Validate dynamic redirect targets before calling res.redirect():
const target = getRedirectTarget(req)
if (target === undefined) return res.sendStatus(400)
res.redirect(303, target)
File and Binary Responses
res.send() accepts a Uint8Array as a binary response body:
res.send(new Uint8Array([0x4f, 0x4b]))
Control ETag generation for an individual file with the etag option:
res.sendFile('/srv/public/archive.zip', { etag: false })
Use array values with res.links() to send multiple targets for one relation:
res.links({
alternate: ['/docs.en', '/docs.fi']
})
For MIME lookup, import mime-types; express.static.mime no longer exists.
Static files and res.sendFile() use the mime-types mappings.
Operational Checks
To retain detailed router traces, enable both the Express and router debug namespace families:
DEBUG=express:*,router,router:* node index.js
Do not rely on the brief extended-query-parser behavior from the initial 5.2.0 release. Upgrade a deployment pinned there and verify query behavior against the installed patch version.
res.render() always behaves asynchronously, even if the view engine exposes
a synchronous implementation. Keep post-render logic in the asynchronous
completion path.
Request bodies may use Brotli encoding when sent by clients that support it. Include encoded-request coverage when middleware or infrastructure handles content encoding.
Verification Checklist
- Confirm the Node.js runtime meets the minimum.
- Search route strings for wildcards, optionals, regexp syntax, and reserved characters.
- Exercise root and non-root wildcard matching separately.
- Verify rejected async work reaches the final error middleware.
- Confirm handlers return native promises.
- Test simple and application-configured query parsing.
- Check behavior when no body parser has populated
req.body. - Verify host-dependent logic with an explicit port.
- Request intended
/.well-knownassets and unrelated dotfiles. - Simulate a listener bind error.
- Validate status, cookie-clearing,
Vary, and redirect calls. - Check MIME-sensitive static and file responses.
- Verify binary responses, per-file ETags, and repeated link relations.
- Run render completion logic through its asynchronous path.
- Enable the router namespaces when comparing routing traces.
Use the linked references for the complete compatibility details and precise batch attribution.