Skip to content

/changelogs/express/express-4-to-5

Express 4 to 5 migration: breaking changes

verified onenvironmentlocal · windows 10.0.26200 arm64 · GNU bash 5.3.15 (Git Bash)updated
tool
Express
versions
4.x → 5.0

Express 5.0.0 was published on npm in September 2024, about ten years after 4.0; since 5.1.0 (March 2025) it is the default latest tag. The changes most projects hit are the stricter route syntax (app.get('*') throws Missing parameter name), removed deprecated method signatures, and automatic handling of rejected promises. The Express team publishes codemods: npx @expressjs/codemod upgrade.

Seen in practice: async errors and removed signatures

The same app under Express 4 and Express 5. In 4 the rejected promise is not handled by Express (the request would hang); in 5 it reaches the error handler. res.send(404) sets the status in 4; in 5 the number is sent as the response body with status 200:

bash
cat > app.js <<'EOF'
const express = require("express");
const app = express();
app.get("/async", async () => { throw new Error("db down"); });
app.get("/old", (req, res) => res.send(404));
app.use((err, req, res, next) => res.status(500).send("handled: " + err.message));
process.on("unhandledRejection", (e) => console.log("  unhandledRejection:", e.message));
const server = app.listen(4403, "127.0.0.1", async () => {
  for (const u of ["/async", "/old"]) {
    const ctl = AbortSignal.timeout(1500);
    try {
      const r = await fetch("http://127.0.0.1:4403" + u, { signal: ctl });
      console.log(" ", u, r.status, (await r.text()).split("\n")[0].slice(0, 60));
    } catch { console.log(" ", u, "no response (timed out)"); }
  }
  server.close(); process.exit(0);
});
EOF
npm init -y >/dev/null
for v in 4 5; do
  npm install --silent --no-audit --no-fund express@$v >/dev/null 2>&1
  echo "--- express $v"
  node app.js 2>&1 | grep -v "deprecated"
done

Output

--- express 4
  unhandledRejection: db down
  /async no response (timed out)
  /old 404 Not Found
--- express 5
  /async 500 handled: db down
  /old 200 404

Breaking changes and fixes

Express 4Express 5
app.get('*'), '/api/*''/{*splat}', '/api/*splat' (named wildcards)
'/users/:id?''/users{/:id}'
res.send(404) sets the statusres.send(404) sends the body 404 with status 200; use res.sendStatus(404)
res.json(obj, 201)res.status(201).json(obj)
res.redirect('/x', 301)res.redirect(301, '/x')
app.del()app.delete()
req.param('id')req.params.id, req.body.id or req.query.id
async errors needed try/catch + next(err)rejected promises go to the error handler automatically
req.body = {} without parserreq.body is undefined without a body parser
req.query writable, extended parserread-only getter; set "query parser" to "extended" for nested objects

Upgrade steps

  1. Upgrade Node.js to 18 or later.
  2. Run npm install express@5 and the codemods (npx @expressjs/codemod upgrade).
  3. Search for '*' and :param? in routes and rewrite them with the new syntax.
  4. Remove try/catch blocks that only forwarded errors to next(err) in async handlers, if you want; they still work.
  5. Check middleware packages for Express 5 support (most popular ones work unchanged).

Sources