Skip to content

Commit

Permalink
feat: v5 release
Browse files Browse the repository at this point in the history
  • Loading branch information
wesleytodd committed Sep 13, 2024
1 parent 243407b commit 3c8ab5e
Showing 1 changed file with 139 additions and 0 deletions.
139 changes: 139 additions & 0 deletions _posts/2024-09-12-v5-release.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
---
title: Express v5
tags: site-admin
author: Wes Todd
---

A lot has happened in the last 10 years, but today we are excited to talk about what has happened in the last 8 months. The Express project has had a renaissance, and we are
excited to finally share with you some of the work we have been doing. As you might be aware, the [v5 release which has been in progress since July 14th
2014](https://github.com/expressjs/express/pull/2237) is now published and merged. There is a lot to talk about in the release, but I want to take a second to recognize the work of
many contributors over the years, especially @dougwilson who tirelessly maintained one of the most stable projects in the ecosystem over the past 10 years. Without the work from so
many, this release would not have happened so if you are among these contributors please give yourself a pat on the back.

So what happened 8 months ago? We went public with a proposed plan to move [Express Forward](https://github.com/expressjs/discussions/issues/160). This plan included re-committing
to the governance we had outlined many years before and added some things to help onboard more contributors to help kickstart progress again. It might not seem important to folks
who are less involved in Open Source, but project governance is critical to larger projects health. I want to thank the [OpenJS Foundation Cross Project
Council](https://github.com/openjs-foundation/cross-project-council/) and it's members for helping us put together this plan.

## So what about v5?

Lets start by mentioning how **boring** this release is meant to be. I know this might seem like an odd thing to say, but it was truly our goal to keep this release as simple as we
could while unblocking ourselves to make larger and more impactful changes in future releases. This means we focused on things like dropping support for older Node.js versions,
addressing long standing security concerns, and updating the projects process to make things more maintainable for maintainers. To most folks these sound pretty **boring**, but to
us this means we can more easily ship future feature releases with the more exciting changes.

Before I move onto the changes for Express users we need to address the timeline and reason we released v5 when we did and on the `next` dist-tag. As part of reviving the project,
we started a [Security Working Group](https://github.com/expressjs/security-wg) and security triage team to help us ensure we address the growing needs around Open Source Supply
Chain Security. As part of this we undertook a security audit (more details to come on that) which uncovered a few things. Additionally while giving this aspect more attention, we
uncovered some other problems which needed to be addressed. This meant that in addition to the normal work we would do in public issues, we had many stacked features being worked
on in private forks. These required orchestration when releasing to enure the code and CVE reports went out together. So while we recognize that not having this blog post, our
changelog, and the documentation updated in advance was not ideal, with our limited contributor base we felt it was more important to focus on getting the secure and stable code
released.

We will be publishing more details on our LTS plans, including dates when we plan to move from `next` to `latest`, as soon as we can. But for now, if you are uncomfortable being on
the bleeding edge (even if it is a rather dull edge) then please consider waiting until we move to `latest` to upgrade. That said, we look forward to working with you all as you
upgrade to support and address any bugs you encounter.

## The Breaking Changes

As I said above, we did the minimum number of breaking changes we could. I try here to list them in order of impact to application owners, but there are a fair number of subtle
chnages burried in here which you should read the changelog for more details on.

### Goodbye Node.js 0.10, Hello Node 18 and up.

We dropped support for Node.js versions lower than 18. This was a long time coming, but it is probably the most important change for us as maintainers of these libraries. Keeping old
Node.js version support was holding us back from many critical performance and maintainability changes. Now that we have dropped these versions we have more stable and maintainable
CI, we can start adopting some newer language and runtime features, and we can drop many dependencies which are no longer required.

We recognize that this might mean some enterprises have difficulty with older or "parked" applications, and because of this we are working on a partnership with HeroDevs to offer
"Never Ending Support" which will include critical security patches even after v4 enters End-of-Life (more on these plans will come soon). That said, we strongly suggest that folks
update to modern Node.js versions as soon as possible.

### Path Matching and Regular Expressions

We updated from `path-to-regexp@0.x` to `path-to-regexp@8.x` in v5, needless to say there were many years of changes in this. If you were using any of the 5.0.0-beta releases, we
landed a last minute update which greatly changed the path semantics so we could [remove any ReDoS chances going forward](https://blakeembrey.com/posts/2024-09-web-redos/). For
more detailed changes, [see the `path-to-regexp` readme](https://github.com/pillarjs/path-to-regexp?tab=readme-ov-file#express--4x).

#### 1. No more regex. We dropped support for sub-expression regular expressions (ex. `/:foo(\\d+)`).

This is one of the most common patterns in wide use which we have removed. The unfortunate nature of regular expressions is how easy it is to write one with exponential time behaviors
when parsing input. The dreaded ReDoS. It turns out it is very difficult to prevent users from doing this, and as a library which converts strings to regular expressions, we are on
the hook for the security aspects of this.

*How to migrate:* We recommend using more robust input validation libraries. [There are many on `npm`](https://www.npmjs.com/search?q=validate%20express) depending on your needs.
Shameless plug from the author, I maintain [a middleware based "code first" OpenAPI library](https://www.npmjs.com/package/@wesleytodd/openapi) for this kind of thing.

#### 2. Splats, optional, and captures oh my. 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.

1. `: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.
2. `:name*` becomes `*name`. (@blakeembrey to provide more details)
3. `:name+` is equivalent to a `*name` and so has been removed
4. 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.

*How to migrate:* (@wesleytodd to provide more info with codemods and migration tools)

#### 3. Name everything. Ordered numerical parameters are not supported.

In Express v4, using regex capture groups you could get numerical parameters (ex. `/user(s?)` => `req.params[0] === 's'`). Now all params must be named. Along with requiring a
name, we now support all valid JavaScript identifiers or quoted (ex. `/:"this"`).

### Promise Support

This one may be a bit contentious, but I `Promise` we are moving in the right direction. We have added support for returned *rejected* promises from middleware raising errors. This
*does not include* calling `next` from returned *resolved* promises. There are a lot of edge cases in old express apps around 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:

```javascript
app.use(async (req, res, next) => {
req.locals.user = await getUser(req);
next();
});
```

Notice that we use `async/await` and the `getUser` call may throw (user does not exist, db is down, etc), but we still call `next` if it is successful. We dont 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)`.

*editorial note:* Error handling is a huge topic, but one hill I will die on is that errors should be handled as close to the error site as possible. So while this is now handled
in the router, I would strongly urge you to catch this kind of error within the middleware and handle it without relying on separate error handling middleware.

### Body Parser

We made an assortment of `body-parser` changes:

- Add option to customize the urlencoded body depth with a default value of 32 (@TODO see CVE)
- Remove deprecated `bodyParser()` combination middleware
- `req.body` is no longer always initialized to `{}`
- `urlencoded` parser now defaults `extended` to false
- Added brotli support

### Removed APIs

We have removed a bunch of apis which were primarily from v3.

- `res.redirect('back')` and `res.location('back')` are no longer a supported magic string, explicitly use `req.get('Referrer') || '/'`
- `res.send(status, body)` - use `res.status(status).send(body)`
- `res.redirect(url, status)` - use `res.redirect(status, url)`
- `res.jsonp(status, obj)` - use `res.status(status).jsonp(obj)`
- `res.json(status, obj)` - use `res.status(status).json(obj)`
- `app.param(fn)` - use `req.params`, `req.body` or `req.query` instead
- `app.del` - use `app.delete`
- `req.acceptsCharset` - use `req.acceptsCharsets`
- `req.acceptsEncoding` - use `req.acceptsEncodings`
- `req.acceptsLanguage` - use `req.acceptsLanguages`
- `res.json(obj, status)` signature - use `res.json(status, obj)`
- `res.jsonp(obj, status)` signature - use `res.jsonp(status, obj)`
- `res.send(body, status)` signature - use `res.send(status, body)`
- `res.send(status)` signature - use `res.sendStatus(status)`
- `res.sendfile` - use `res.sendFile` instead

## Our work is just starting

I know it seems like an odd thing to announce a "boring" major version release and then say the work is only starting after 10 years, but we hope that the ecosystem sees how
important this release is as a milestone toward a server side JavaScript ecosystem which is 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 do our best to help the ecosystem move forward with these goals in mind. If you would like to support this work,
which we do on a volunteer basis, please consider supporting the project and it's maintainers via our sponsorship opportunities (@TODO link here).

0 comments on commit 3c8ab5e

Please sign in to comment.