Introducing Express v5: A New Era for the Node.js Framework
Ten years ago (July 2014) the Express v5 release pull request was opened, and now at long last it’s been merged and published!
We want to recognize the work of all our contributors, especially Doug Wilson, who spent the last ten years ensuring Express was the most stable project around. Without his contributions and those of many others, this release could not have happened.
Eight months ago we went public with a plan to move Express forward. This plan included re-committing to the governance outlined years ago and adding more contributors to help kickstart progress. Many people may not realize that robust project governance is critical to the health of a large open-source project. We want to thank the OpenJS Foundation Cross Project Council and its members for helping us put together this plan.
So what about v5?
This release is designed to be boring! That may sound odd, but we’ve intentionally kept it simple to unblock the ecosystem and enable more impactful changes in future releases. This is also about signaling to the Node.js ecosystem that Express is moving again. The focus of this release is on dropping old Node.js version support, addressing security concerns, and simplifying maintenance.
Before going into the changes in this release, let’s address why it was released v5 on the next
dist-tag. As part of reviving the project, we started a Security working group and security triage team to address the growing needs around open source supply chain security. We undertook a security audit (more details to come on that) and uncovered some problems that needed to be addressed. Thus, in addition to the “normal” work done in public issues, we also did a lot of security work in private forks.
This security work required orchestration when releasing, to ensure the code and CVE reports went out together. You can find a summary of the most recent vulnerabilities patched in our security release notes.
While we weren’t able to simultaneously release v5, this blog post, the changelog, and documentation, we felt it was most important to have a secure and stable release.
As soon as possible, we’ll provide more details on our long-term support (LTS) plans, including when the release will move from next
to latest
. For now, if you are uncomfortable being on the bleeding edge (even if it is a rather dull edge) then you should wait to upgrade until the release is tagged latest
. That said, we look forward to working with you to address any bugs you encounter as you upgrade.
Breaking changes
The v5 release has the minimum possible number of breaking changes, listed here in order of impact to applications.
- Ending support for old Node.js versions
- Changes to path matching and regular expressions
- Promise support
- Body parser changes
- Removing deprecated method signatures
There are also a number of subtle changes: for details, see Migrating to Express 5.
Ending support for old Node.js versions
Goodbye Node.js 0.10, hello Node 18 and up!
This release drops support for Node.js versions before v18. This is an important change because supporting old Node.js versions has been holding back many critical performance and maintainability changes. This change also enables more stable and maintainable continuous integration (CI), adopting new language and runtime features, and dropping dependencies that are no longer required.
We recognize that this might cause difficulty for some enterprises with older or “parked” applications, and because of this we are working on a partnership with HeroDevs to offer “never-ending support” that will include critical security patches even after v4 enters end-of-life (more on these plans soon). That said, we strongly suggest that you update to modern Node.js versions as soon as possible.
Changes to path matching and regular expressions
The v5 releases updates to path-to-regexp@8.x
from path-to-regexp@0.x
, which incorporates many years of changes. If you were using any of the 5.0.0-beta releases, a last-minute update which greatly changed the path semantics to remove the possibility of any ReDoS attacks. For more detailed changes, see the path-to-regexp
readme.
No more regex
This release no longer supports “sub-expression” regular expressions, for example /:foo(\\d+)
.
This is a commonly-used pattern, but we removed it for security reasons. Unfortunately, it’s easy to write a regular expression that has exponential time behavior when parsing input: The dreaded regular expression denial of service (ReDoS) attack. It’s very difficult to prevent this, but as a library that converts strings to regular expressions, we are on the hook for such security aspects.
How to migrate: The best approach to prevent ReDoS attacks is to use a robust input validation library. There are many on npm
depending on your needs. TC member Wes Todd maintains a middleware-based “code first” OpenAPI library for this kind of thing.
Splats, optional, and captures oh my
This release includes simplified patterns for common route patterns. With the removal of regular expression semantics comes other small but impactful changes to how you write your routes.
:name?
becomes{:name}
. Usage of{}
for optional parts of your route means you can now do things like/base{/:optional}/:required
and what parts are actually optional is much more explicit.*
becomes*name
.- New reserved characters:
(
,)
,[
,]
,?
,+
, &!
. These have been reserved to leave room for future improvements and to prevent mistakes when migrating where those characters mean specific things in previous versions.
Name everything
This release no longer supports ordered numerical parameters.
In Express v4, you could get numerical parameters using regex capture groups (for example, /user(s?)
=> req.params[0] === 's'
). Now all parameters must be named. Along with requiring a name, Express now supports all valid JavaScript identifiers or quoted (for example, /:"this"
).
Promise support
This one may be a bit contentious, but we “promise” we’re moving in the right direction. We added support for returned rejected promises from errors raised in middleware. This does not include calling next
from returned resolved promises. There are a lot of edge cases in old Express apps that have expectations of Promise
behavior, and before we can run we need to walk. For most folks, this means you can now write middleware like the following:
app.use(async (req, res, next) => {
req.locals.user = await getUser(req);
next();
});
Notice that this example uses async/await
and the getUser
call may throw an error (if, for example, the user doesn’t exist, the user database is down, and so on), but we still call next
if it is successful. We don’t need to catch the error in line anymore if we want to rely on error-handling middleware instead because the router will now catch the rejected promise and treat that as calling next(err)
.
NOTE: Best practice is to handle errors as close to the site as possible. So while this is now handled in the router, it’s best to catch the error in the middleware and handle it without relying on separate error-handling middleware.
Body parser changes
There are a number of body-parser
changes:
- Add option to customize the urlencoded body depth with a default value of 32 as mitigation for CVE-2024-45590 (technical details)
- Remove deprecated
bodyParser()
combination middleware req.body
is no longer always initialized to{}
urlencoded
parser now defaultsextended
to false- Added support for Brotli lossless data compression
Removing deprecated method signatures
Express v5 removes a number of deprecated method signatures, many of which were carried over from v3. Below are the changes you need to make:
res.redirect('back')
andres.location('back')
: The magic string'back'
is no longer supported. Usereq.get('Referrer') || '/'
explicitly instead.res.send(status, body)
andres.send(body, status)
signatures: Useres.status(status).send(body)
.res.send(status)
signature: Useres.sendStatus(status)
for simple status responses, orres.status(status).send()
for sending a status code with an optional body.res.redirect(url, status)
signature: Useres.redirect(status, url)
.res.json(status, obj)
andres.json(obj, status)
signatures: Useres.status(status).json(obj)
.res.jsonp(status, obj)
andres.jsonp(obj, status)
signatures: Useres.status(status).jsonp(obj)
.app.param(fn)
: This method has been deprecated. Instead, access parameters directly viareq.params
, or usereq.body
orreq.query
as needed.app.del('/', () => {})
method: Useapp.delete('/', () => {})
instead.req.acceptsCharset
: Usereq.acceptsCharsets
(plural).req.acceptsEncoding
: Usereq.acceptsEncodings
(plural).req.acceptsLanguage
: Usereq.acceptsLanguages
(plural).res.sendfile
method: Useres.sendFile
instead.
As a framework, we aim to ensure that the API is as consistent as possible. We’ve removed these deprecated signatures to make the API more predictable and easier to use. By streamlining each method to use a single, consistent signature, we simplify the developer experience and reduce confusion.
Migration and security guidance
For developers looking to migrate from v4 to v5, there’s a detailed migration guide to help you navigate through the changes and ensure a smooth upgrade process.
Additionally, we’ve been working hard on a comprehensive Threat Model that helps illustrate our philosophy of a “Fast, unopinionated, minimalist web framework for Node.js.” It provides critical insights into areas like user input validation and security practices that are essential for safe and secure usage of Express in your applications.
Our work is just starting
We see the v5 release as a milestone toward an Express ecosystem that’s a stable and reliable tool for companies, governments, educators, and hobby projects. It is our commitment as the new stewards of the Express project to move the ecosystem forward with this goal in mind. If you want to support this work, which we do on a volunteer basis, please consider supporting the project and its maintainers via our sponsorship opportunities.
We have an extensive working backlog of tasks, PRs, and issues for Express and dependencies. Naturally, we expect developers will continue to report issues to add to this backlog and open PRs moving forward, and we’ll continue to collaborate with the community to triage and resolve them. We look forward to continuing to improve Express and making it useful for its users across the world.