Check the runtime and keep the codemod in perspective
Express 5 requires Node.js 18 or higher, so a service below that moves the runtime first. The Express migration guide also offers a codemod recipe for mechanical changes. A codemod can rewrite a removed method, but it cannot tell you whether a rewritten route still matches the URLs your users and integrations send. That needs a before-and-after check of real URLs.
- Run the codemod on a branch and read its diff.
- Treat its output as a draft to be tested, not a result.
Path patterns change meaning
In Express 5 a wildcard must be named, so a bare asterisk route becomes something like /*splat, and /{*splat} also matches the root path. Optional parameters use braces instead of a question mark. Regular-expression characters in a path are no longer allowed, and the characters ()[]?+! must be escaped. Parameter names must be valid identifiers. Wildcard parameters arrive as arrays, and parameters that did not match are omitted. A pattern that stops matching, or starts matching more, can still start the app.
- List every route string that contains an asterisk, question mark, bracket or parenthesis.
- For each, write two URLs that must match and two that must not, and run them before and after.
Errors in async handlers now reach your error handler
The guide says async handlers and middleware that throw or reject now forward the error to the error handler as if by calling next with the error. A failure that previously did not reach your error handler may now do so, so check that the handler returns a safe response and logs without exposing internals. Add one test that throws and one that rejects inside an async route.
- Look at what the error handler returns to the client, not only that it runs.
- Do not remove existing try/catch blocks in the same change.
Request and response details that moved
req.body is undefined when no parser ran, req.query is a read-only getter whose default parser is simple, and express.urlencoded no longer defaults to extended parsing. req.host keeps the port, res.status accepts only integers from 100 to 999 and res.clearCookie ignores maxAge and expires. Static dotfiles are ignored by default, including hidden directories, so a .well-known path returns 404 unless you allow dotfiles. app.listen now passes server errors such as an address-in-use failure to its callback.
- Search for code that assigns to req.query or reads req.body without a parser.
- If you serve .well-known files, test them explicitly.
Removed methods, and how the fixed job is accepted
Removed forms include app.del, req.param, the status-argument forms of json, send and redirect, and redirect to the string 'back'. The fixed Express 4 to 5 job is accepted when the agreed URL table returns the same status and response shape on both versions, every converted pattern has matching and non-matching samples, async error tests reach your handler, no removed form remains and the existing tests pass. Send the lockfile version and the list of route strings, not code. Prices are untested proposals and payment follows the agreed checks.
Sources and limits
- Express: migrating to Express 5 Checked 2026-10-11.
- Express 5 requires Node.js 18 or higher and the guide offers a codemod recipe for some changes.
- Wildcards in paths must be named, such as /*splat; optional parameters use braces; regular-expression characters are no longer allowed and the characters ()[]?+! must be escaped.
- Async handlers and middleware that throw or reject forward the error to the error handler; req.body is undefined when not parsed; req.query is a read-only getter; express.urlencoded extended defaults to false; static dotfiles are ignored by default including hidden directories, so .well-known paths return 404 unless allowed.
- app.del, req.param, res.json(obj, status) forms, res.redirect('back') and several other forms are removed; app.listen passes server errors to its callback.