Express.js, the fast, unopinionated web framework for Node.js, has released version 5 with several breaking changes and improvements. While Express 5 maintains much of the core API from Express 4, migrating requires careful attention to deprecated methods, updated syntax, and new behaviors. This guide walks you through everything you need to know to upgrade your application smoothly.
Why Upgrade to Express 5?
Express 5 brings modern JavaScript support, improved error handling, and performance enhancements. Key benefits include:
- Brotli compression support for faster responses.
- Better async/await error handling for cleaner code.
- Security and performance improvements under the hood.
- Future-proofing your application as Express 4 approaches end-of-life.
Prerequisites
Before upgrading:
- Ensure Node.js 18 or higher is installed.
- Backup your project.
- Run tests to establish a baseline for post-migration validation.
Step 1: Install Express 5
Update your dependencies:
npm install "express@5"
Step 2: Automate Fixes with Codemods
Express provides codemods to automate many breaking changes. Run them to save time:
All Codemods
npx @expressjs/codemod upgrade
Target Specific Fixes
- v4-deprecated-signatures – Fixes deprecated method signatures (e.g.,
res.send(status)
). - pluralized-methods – Updates pluralized method names like
req.acceptsCharsets()
. - req-param – Replaces
req.param()
withreq.params
/req.body
. - magic-redirect – Replaces
res.redirect('back')
withreq.get('Referrer')
.
Example: Fix deprecated response methods:
npx @expressjs/codemod v4-deprecated-signatures
Step 3: Address Breaking Changes
1. Removed Methods and Properties
a. app.del()
→ app.delete()
Express 5 removes app.del()
in favor of app.delete()
, which aligns with HTTP method naming.
// Express 4
app.del("/user/:id", (req, res) => {
/* ... */
});
// Express 5
app.delete("/user/:id", (req, res) => {
/* ... */
});
b. res.sendfile()
→ res.sendFile()
Use the camel-cased res.sendFile()
:
// Before
res.sendfile("image.png");
// After
res.sendFile("image.png");
c. req.param(name)
→ Explicit Sources
Avoid the ambiguous req.param()
and use specific sources:
// Before
const id = req.param("id"); // Searches params, body, and query
// After
const id = req.params.id || req.body.id || req.query.id;
d. Response Method Signatures
Express 4 allowed res.send(body, status)
, but Express 5 requires chaining .status()
first:
// Before
res.send({ user: "John" }, 200);
res.json({ error: "Not found" }, 404);
// After
res.status(200).send({ user: "John" });
res.status(404).json({ error: "Not found" });
2. Path Route Syntax Updates
Express 5 enforces stricter path matching rules:
a. Named Wildcards
Wildcards (*
) must have a name:
// Before: Catch-all route
app.get("*", (req, res) => {
/* ... */
});
// After: Name the wildcard
app.get("/*splat", (req, res) => {
/* ... */
});
b. Optional Parameters with {}
Use braces instead of ?
for optional parameters:
// Before
app.get("/:file.:ext?", (req, res) => {
/* ... */
});
// After
app.get("/:file{.:ext}", (req, res) => {
/* ... */
});
c. Escape Special Characters
Escape regex-like characters (e.g., [
, ]
):
// Before (ambiguous)
app.get("/[page]/:id", (req, res) => {
/* ... */
});
// After
app.get("/\\[page\\]/:id", (req, res) => {
/* ... */
});
3. Behavior Changes
a. req.query
is No Longer Writable
In Express 4, you could modify req.query
. In Express 5, it’s a read-only getter.
b. req.body
Defaults to undefined
Ensure body parsers (like express.json()
) are properly configured, as req.body
is no longer {}
by default.
c. express.urlencoded()
Uses extended: false
Update configurations if you relied on nested objects:
// Before (Express 4 default)
app.use(express.urlencoded({ extended: true }));
// After (Express 5 default)
app.use(express.urlencoded({ extended: false })); // Explicitly set if needed
4. Error Handling Improvements
Unhandled promise rejections in async middleware now forward errors to Express error handlers:
// Express 5 automatically catches this
app.get("/", async (req, res) => {
throw new Error("Async error!");
});
// Error handler middleware
app.use((err, req, res, next) => {
res.status(500).send(err.message);
});
5. Debugging Changes
Update debug namespaces to include router
logs:
# Before
DEBUG=express:* node app.js
# After
DEBUG=express:*,router,router:* node app.js
Step 4: Leverage New Features
1. Brotli Compression
Express 5 supports Brotli encoding out of the box. Enable it with:
const compression = require("compression");
app.use(compression({ br: true })); // Enable Brotli
2. Improved res.render()
View engines must now be asynchronous, avoiding race conditions in rendering.
Common Pitfalls to Avoid
- Ignoring Deprecation Warnings: Fix all warnings during testing.
- Wildcard Route Syntax: Test routes thoroughly after renaming
*
to/*splat
. - Query Parameter Reliance: Verify
req.query
behavior if your app parses complex data.
Migration Checklist
- Update to Node.js 18+.
- Run codemods (
npx @expressjs/codemod upgrade
). - Replace
app.del()
withapp.delete()
. - Update response methods (
res.status(code).send()
). - Adjust path syntax (named wildcards, escaped characters).
- Test error handling for async routes.
- Validate
req.body
andreq.query
behavior. - Update debug namespaces if used.
Final Thought
Migrating to Express 5 requires diligence, but the payoff is worth it: better performance, modern JavaScript support, and improved error handling. Use codemods to automate repetitive tasks, test rigorously, and refer to the Express 5 documentation for details. By following this guide, you’ll future-proof your application and unlock new features with minimal downtime.
Happy coding! 🚀