升级到 Express v5
Express 5 与 Express 4 差异不大;尽管基础 API 保持一致,但仍存在部分导致无法向下兼容的改动。 因此,基于 Express 4 构建的应用在升级至 Express 5 后可能无法正常运行。
安装
安装该版本需要 Node.js 18 及以上版本。 随后在应用目录中执行以下命令:
npm install "express@5"yarn add "express@5"pnpm add "express@5"bun add "express@5"随后你可以运行自动化测试来排查失败项,并根据下方列出的更新内容修复问题。 解决测试失败问题后,运行应用查看出现的错误。 你可以立刻发现应用是否使用了任何不受支持的方法或属性。
Express 5 Codemods
为帮助你迁移 Express 服务器,我们提供了一套代码转换工具,可自动将代码更新至最新版 Express。
运行以下命令以执行所有可用的代码转换:
npx codemod@latest @expressjs/v5-migration-recipeyarn dlx codemod@latest @expressjs/v5-migration-recipepnpm dlx codemod@latest @expressjs/v5-migration-recipebunx codemod@latest @expressjs/v5-migration-recipe若要运行特定的代码转换,可执行以下命令:
npx codemod@latest @expressjs/name-of-the-codemodyarn dlx codemod@latest @expressjs/name-of-the-codemodpnpm dlx codemod@latest @expressjs/name-of-the-codemodbunx codemod@latest @expressjs/name-of-the-codemod可在此处查看可用的代码转换列表。
移除方法和属性
如果你的应用中使用了上述任意方法或属性,应用将会崩溃。 因此,更新到 5 版本后,你需要修改你的应用。
app.del()
Express 5 不再支持 app.del() 函数。 如果您使用此函数,将会出现错误。 要注册 HTTP DELETE 路由,请使用 app.delete() 函数。
起初,使用 del 而非 delete,因为 delete 是 JavaScript 中的保留关键字。 但从 ECMAScript 6 开始,delete 及其他保留关键字可以合法用作属性名。
如何更新
你可以通过运行以下命令自动更新你的代码:
npx codemod@latest @expressjs/route-del-to-deleteyarn dlx codemod@latest @expressjs/route-del-to-deletepnpm dlx codemod@latest @expressjs/route-del-to-deletebunx codemod@latest @expressjs/route-del-to-delete或者您可以手动更新您的代码:
app.del('/user/:id', (req, res) => {app.delete('/user/:id', (req, res) => { res.send(`DELETE /user/${req.params.id}`);});app.param(fn)
app.param(fn) 签名用于修改 app.param(name, fn) 函数的行为。 该方法自 v4.11.0 起已被弃用,Express 5 完全不再支持它。
复数形式的方法名
以下方法名已改为复数形式。 在 Express 4 中,使用旧方法会触发弃用警告。 Express 5 已完全不再支持这些方法:
req.acceptsCharset()替换为req.acceptsets()。
req.acceptsEncoding() 替换为 req.acceptsEncodings() 。
req.acceptsLanguage() 替换为 req.acceptsLanguages() 。
如何更新
你可以通过运行以下命令自动更新你的代码:
npx codemod@latest @expressjs/pluralize-method-namesyarn dlx codemod@latest @expressjs/pluralize-method-namespnpm dlx codemod@latest @expressjs/pluralize-method-namesbunx codemod@latest @expressjs/pluralize-method-names或者您可以手动更新您的代码:
app.all('/', (req, res) => { req.acceptsCharset('utf-8'); req.acceptsEncoding('br'); req.acceptsLanguage('en'); req.acceptsCharsets('utf-8'); req.acceptsEncodings('br'); req.acceptsLanguages('en');
// ...});app.param(name, fn) 中名称前的冒号(:)
app.param(name, fn) 函数中名称开头的冒号(:)是 Express 3 的遗留用法,为了向后兼容,Express 4 虽支持该用法但会抛出弃用提示。 Express 5 会静默忽略该冒号,并直接使用不带冒号前缀的名称参数。
This should not affect your code if you follow the Express 4 documentation of app.param, as it makes no mention of the leading colon.
req.param(name)
这种可能造成混淆且存在风险的表单数据获取方法已被移除。 现在你需要在 req.params、req.body 或 req.query 对象中显式查找提交的参数名称。
如何更新
你可以通过运行以下命令自动更新你的代码:
npx codemod@latest @expressjs/explicit-request-paramsyarn dlx codemod@latest @expressjs/explicit-request-paramspnpm dlx codemod@latest @expressjs/explicit-request-paramsbunx codemod@latest @expressjs/explicit-request-params或者您可以手动更新您的代码:
app.post('/user', (req, res) => { const id = req.param('id'); const body = req.param('body'); const query = req.param('query'); const id = req.params.id; const body = req.body; const query = req.query;
// ...});res.json(obj, status)
Express 5 不再支持 res.json(obj, status) 签名。 取而代之的是,先设置状态码,再链式调用 res.json() 方法,写法如下:res.status(status).json(obj)。
如何更新
你可以通过运行以下命令自动更新你的代码:
npx codemod@latest @expressjs/status-send-orderyarn dlx codemod@latest @expressjs/status-send-orderpnpm dlx codemod@latest @expressjs/status-send-orderbunx codemod@latest @expressjs/status-send-order或者您可以手动更新您的代码:
app.post('/user', (req, res) => { res.json({ name: 'Ruben' }, 201); res.status(201).json({ name: 'Ruben' });});res.jsonp(obj, status)
Express 5 不再支持 res.jsonp(obj, status) 签名。 取而代之的是,先设置状态码,再链式调用 res.jsonp() 方法,写法如下:res.status(status).jsonp(obj)。
如何更新
你可以通过运行以下命令自动更新你的代码:
npx codemod@latest @expressjs/status-send-orderyarn dlx codemod@latest @expressjs/status-send-orderpnpm dlx codemod@latest @expressjs/status-send-orderbunx codemod@latest @expressjs/status-send-order或者您可以手动更新您的代码:
app.post('/user', (req, res) => { res.jsonp({ name: 'Ruben' }, 201); res.status(201).jsonp({ name: 'Ruben' });});res.redirect(url, status)
Express 5 不再支持 res.redirect(url, status) 签名。 相反,使用下列签名:res.redirect(status, url)。
如何更新
你可以通过运行以下命令自动更新你的代码:
npx codemod@latest @expressjs/redirect-arg-orderyarn dlx codemod@latest @expressjs/redirect-arg-orderpnpm dlx codemod@latest @expressjs/redirect-arg-orderbunx codemod@latest @expressjs/redirect-arg-order或者您可以手动更新您的代码:
app.get('/user', (req, res) => { res.redirect('/users', 302); res.redirect(302, '/users');});
// A redirect that relies on the default 302 status is unaffectedapp.get('/admin', (req, res) => { res.redirect('/dashboard');});res.redirect(‘back’) 与 res.location(‘back’)
Express 5 不再支持 res.redirect() 和 res.location() 方法中的魔法字符串 back。 取而代之,请使用 req.get('Referrer') || '/' 来重定向回上一页。 在 Express 4 中,res.redirect('back') 和 res.location('back') 方法已被弃用。
如何更新
你可以通过运行以下命令自动更新你的代码:
npx codemod@latest @expressjs/back-redirect-deprecatedyarn dlx codemod@latest @expressjs/back-redirect-deprecatedpnpm dlx codemod@latest @expressjs/back-redirect-deprecatedbunx codemod@latest @expressjs/back-redirect-deprecated或者您可以手动更新您的代码:
app.get('/user', (req, res) => { res.redirect('back'); res.redirect(req.get('Referrer') || '/');});res.send(body, status)
Express 5 不再支持 res.send(obj, status) 这种签名。 取而代之的是,先设置状态码,再链式调用 res.send() 方法,写法如下:res.status(status).send(obj)。
如何更新
你可以通过运行以下命令自动更新你的代码:
npx codemod@latest @expressjs/status-send-orderyarn dlx codemod@latest @expressjs/status-send-orderpnpm dlx codemod@latest @expressjs/status-send-orderbunx codemod@latest @expressjs/status-send-order或者您可以手动更新您的代码:
app.get('/user', (req, res) => { res.send({ name: 'Ruben' }, 200); res.status(200).send({ name: 'Ruben' });});res.send(status)
Express 5 不再支持签名 res.send(status) 是一个数字。 取而代之,请使用 res.sendStatus(statusCode) 函数,该函数会设置 HTTP 响应头状态码并发送该状态码的文本版本:“未找到”、“服务器内部错误”等。
如果需要通过 res.send() 函数发送数字,请将数字用引号包裹转换为字符串,避免 Express 将其误判为尝试使用已不支持的旧签名。
如何更新
你可以通过运行以下命令自动更新你的代码:
npx codemod@latest @expressjs/status-send-orderyarn dlx codemod@latest @expressjs/status-send-orderpnpm dlx codemod@latest @expressjs/status-send-orderbunx codemod@latest @expressjs/status-send-order或者您可以手动更新您的代码:
app.get('/user', (req, res) => { res.send(200); res.sendStatus(200);});res.sendfile()
在 Express 5 中,res.sendfile() 函数已替换为驼峰命名的版本 res.sendFile()。
如何更新
你可以通过运行以下命令自动更新你的代码:
npx codemod@latest @expressjs/camelcase-sendfileyarn dlx codemod@latest @expressjs/camelcase-sendfilepnpm dlx codemod@latest @expressjs/camelcase-sendfilebunx codemod@latest @expressjs/camelcase-sendfile或者您可以手动更新您的代码:
app.get('/user', (req, res) => { res.sendfile('/path/to/file'); res.sendFile('/path/to/file');});res.sendFile() options
res.sendFile() 的 hidden 和 from 选项已不再支持。 使用 dotfiles 和 root 代替。
The dotfiles option applies to hidden directories in the path as well as hidden files. For example, a file served from an absolute path like /var/www/app/.cache/index.html now requires dotfiles: 'allow', even though index.html is not a dotfile. In Express 4 a hidden directory in the path was served by default; Express 5 returns 404 unless you opt in.
This check only applies to the part of the path that send evaluates. When you pass a root, only the portion relative to root is checked, so a hidden directory inside root is unaffected.
如何更新
你可以通过运行以下命令自动更新你的代码:
npx codemod@latest @expressjs/sendfile-optionsyarn dlx codemod@latest @expressjs/sendfile-optionspnpm dlx codemod@latest @expressjs/sendfile-optionsbunx codemod@latest @expressjs/sendfile-options或者您可以手动更新您的代码:
app.get('/files/:name', (req, res) => { res.sendFile(req.params.name, { hidden: true, from: '/uploads' }); res.sendFile(req.params.name, { dotfiles: 'allow', root: '/uploads' });});If you serve an absolute path that contains a hidden directory, opt in with dotfiles: 'allow' or use root so the hidden segment is not part of the evaluated path:
app.get('/build', (req, res) => { res.sendFile('/var/www/app/.cache/index.html'); res.sendFile('/var/www/app/.cache/index.html', { dotfiles: 'allow' }); // or: res.sendFile('index.html', { root: '/var/www/app/.cache' });});express.static() options
不再支持 expres.static() 的 hidden 和 from 两个选项。 使用 dotfiles 和 root 代替。 注意,from 从未在 API 文档中记载,但曾被接受作为 root 的别名。 dotfiles的默认值现在是 `“ignore”。
The dotfiles check now also applies to hidden directories in the request path, not just hidden files. A request like GET /.well-known/acme-challenge/... that was served by default in Express 4 now returns 404 unless you set dotfiles: 'allow'. This only affects the part of the path relative to the configured root; a hidden directory in root itself is unaffected.
如何更新
你可以通过运行以下命令自动更新你的代码:
npx codemod@latest @expressjs/static-dotfilesyarn dlx codemod@latest @expressjs/static-dotfilespnpm dlx codemod@latest @expressjs/static-dotfilesbunx codemod@latest @expressjs/static-dotfiles或者您可以手动更新您的代码:
const express = require('express');const app = express();
app.use(express.static('public', { hidden: true }));app.use(express.static('public', { dotfiles: 'allow' }));If you rely on serving a hidden directory such as .well-known (for example, ACME/Let’s Encrypt challenges), opt in explicitly. This is needed even if you never used the hidden option:
app.use(express.static('public'));app.use(express.static('public', { dotfiles: 'allow' }));router.param(fn)
router.param(fn) 签名曾用于修改 router.param(name, fn) 函数的行为。 该方法自 v4.11.0 起已被弃用,Express 5 完全不再支持它。
express.static.mime
在 Express 5 中,mime 不再是 static 字段的导出属性。
请使用 mime-types 包 处理 MIME 类型值。
如何更新
你可以通过运行以下命令自动更新你的代码:
npx codemod@latest @expressjs/static-mimeyarn dlx codemod@latest @expressjs/static-mimepnpm dlx codemod@latest @expressjs/static-mimebunx codemod@latest @expressjs/static-mime或者您可以手动更新您的代码:
express.static.mime.lookup('json');const mime = require('mime-types');mime.lookup('json');MIME 类型更改
由于mime-db 的更新,几种MIME类型已经更改。 这些更改只会影响 expres.static() 和 res.sendFile() 。 完整的更改列表见mime-db更新日志。
Express 4 使用 1.52.0 版本的 mime-db,而 Express 5 使用了更新的版本,以反映 IANA 及其他 MIME 类型规范的更新。 最显著的变化是,JavaScript 文件(.js)现在以 text/javascript 提供服务,而非 application/javascript。
在 Express 5 中,由 mime-db 更新带来的 MIME 类型变更不被视为破坏性变更,因此在更新依赖项时请务必谨慎,因为 MIME 类型可能在次要版本或补丁版本之间发生变化。
express:router 调试日志
路由器处理逻辑现在由 Express 团队维护的独立依赖项(router)执行,因此调试日志已迁移至其他命名空间。 在Express 5.1之前,这些调试日志不存在。 若要获取这些日志,请更新至最新的 Express 5 版本,或更新 package-lock.json 中的 router 包:
| v4 | v5 |
|---|---|
express:router | router |
express:router:layer | router:layer |
express:router:route | router:route |
express:* (includes all) | express:* + router + router:* |
如何更新
DEBUG=express:* node index.jsDEBUG=express:*,router,router:* node index.js更改
这些 API 仍然保留,但行为已发生变更。 请检查这些变更,确保你的应用正常运行。
路径路由匹配语法
路径路由匹配语法是指将字符串作为第一个参数传递给 app.all()、app.use()、app.METHOD()、router.all()、router.METHOD() 和 router.use() 这些 API。 已对路径字符串与传入请求的匹配规则做出如下修改:
-
通配符
*必须指定名称,和参数标记:的使用规则保持一致,请使用/*splat而非/*app.get('/*', async (req, res) => {app.get('/*splat', async (req, res) => {res.send('ok');});Note
*splat匹配没有根路径的任何路径。 如果你需要同时匹配根路径,你可以使用/{*splat}来包装括号中的通配符。app.get('/{*splat}', async (req, res) => {res.send('ok');}); -
可选标识
?不再受支持,请改用大括号写法。app.get('/:file.:ext?', async (req, res) => {app.get('/:file{.:ext}', async (req, res) => {res.send('ok');}); -
路由路径中不支持正则表达式字符。 举个例子:
app.get('/[discussion|page]/:slug', async (req, res) => {app.get(['/discussion/:slug', '/page/:slug'], async (req, res) => {res.status(200).send('ok');}); -
部分字符已被预留保留以规避升级时的歧义(
()[]?+!),如需使用请通过\进行转义。 -
参数名称现支持合法的 JavaScript 标识符,或使用
:"this"形式进行引号包裹。
处理来自中间件和处理器的被拒绝 Promise
返回被拒绝 Promise 的请求中间件和处理程序,现会将被拒绝的值作为 Error 转发给错误处理中间件进行处理。 这意味着使用 async 函数编写中间件和处理程序变得前所未有的简便。 当在 async 函数中抛出错误,或在异步函数内 await 了一个被拒绝的 Promise 时,这些错误将被传递给错误处理程序,效果等同于调用 next(err)。
Details of how Express handles errors is covered in the error handling documentation.
如何更新
你现在可以直接使用 async/await,无需手动捕获错误。 若 getUserById 抛出错误或被拒绝,next 会自动以被拒绝的值作为参数被调用。
app.get('/user/:id', (req, res, next) => { getUserById(req.params.id) .then((user) => res.send(user)) .catch(next);});app.get('/user/:id', async (req, res) => { const user = await getUserById(req.params.id); res.send(user);});express.urlencoded
express.urlencoded 方法的 extended 选项默认值为 false。
如何更新
如果你的应用依赖 extended 特性,请将其显式设置为 true:
app.use(express.urlencoded());app.use(express.urlencoded({ extended: true }));express.static dotfiles
express.static 中间件的 dotfiles 选项现在默认值为 "ignore"。 在 Express 4 中,点文件默认提供服务。 因此,以点(.)开头的目录(如 .well-known)内的文件将不再可访问,并会返回404 未找到错误。 这可能会破坏依赖于提供点目录服务的功能,例如 Android 应用链接和 Apple 通用链接。
如何更新
使用 dotfiles: "allow" 选项显式提供特定的点目录服务。 这使你可以安全地仅提供预期的点目录,同时对其他点文件保持默认的安全行为。
app.use('/.well-known', express.static('public/.well-known', { dotfiles: 'allow' }));app.use(express.static('public'));router.param() with an array of names
router.param(name, fn) no longer accepts an array for name. In Express 4 an array was accepted silently; in Express 5, passing anything other than a string throws TypeError: argument name must be a string. (Note that app.param() still accepts an array of names.)
如何更新
Register each parameter name with its own router.param() call:
router.param(['id', 'page'], (req, res, next, value) => { // ...});const loadParam = (req, res, next, value) => { // ...};router.param('id', loadParam);router.param('page', loadParam);app.listen
在 Express 5 中,当服务器接收到错误事件时,app.listen 方法会调用用户提供的回调函数(若已指定)。 在 Express 4 中,这类错误会被直接抛出。 在 Express 5 中,此项变更将错误处理的职责转移给了回调函数。 如果发生错误,错误将作为参数传递给回调函数。
举个例子:
const server = app.listen(8080, '0.0.0.0', (error) => { if (error) { throw error; // e.g. EADDRINUSE } console.log(`Listening on ${JSON.stringify(server.address())}`);});app.router
在 Express 4 中被移除的 app.router 对象,在 Express 5 中重新回归。 在新版本中,该对象只是对基础 Express 路由的引用,这与 Express 3 不同,在 Express 3 中应用必须显式加载它。
req.body
当请求体尚未被解析时,req.body 属性会返回 undefined。 在Express 4中,默认情况下返回 {} 。
app.post('/user', (req, res) => { console.dir(req.body); // Express 4 // => {} // Express 5 // => undefined});req.host
在Express 4中,如果存在req.host函数,它会不正确地删除端口号。 Express 5中保留端口号。
req.params
当使用字符串路径时,req.params对象现在有一个 null 原型 。 但是,如果路径使用正则表达式定义,req.params 仍为带有标准原型的普通对象。 此外,还有两项重要的行为变更:
WildCard 参数现在是数组:
Wildcards(例如 /*splat)会将路径片段捕获为数组,而非单个字符串。
app.get('/*splat', (req, res) => { // GET /foo/bar console.dir(req.params); // => [Object: null prototype] { splat: [ 'foo', 'bar' ] }});未匹配参数:
在 Express 4 中,不匹配的通配符为空字符串(''),可选 : 参数(使用 ?)会存在键且值为 undefined。 在 Express 5 中,不匹配的参数会从 req.params 中完全省略。
// v4: unmatched wildcard is empty stringapp.get('/*', (req, res) => { // GET / console.dir(req.params); // => { '0': '' }});
// v4: unmatched optional param is undefinedapp.get('/:file.:ext?', (req, res) => { // GET /image console.dir(req.params); // => { file: 'image', ext: undefined }});
// v5: unmatched optional param is omittedapp.get('/:file{.:ext}', (req, res) => { // GET /image console.dir(req.params); // => [Object: null prototype] { file: 'image' }});req.query
req.query 属性不再是可写属性,而是一个 getter。 默认查询解析器已从 “extended” 更改为 “simple”。
app.get('/search', (req, res) => { // This is no longer possible in Express 5 req.query.page = 1;});res.clearCookie
res.clearCookie 方法会忽略用户提供的 maxAge 和 expires 选项。
app.get('/logout', (req, res) => { res.clearCookie('session', { maxAge: 0, expires: new Date(0) }); res.clearCookie('session');});res.status
res.status 方法仅接受 100 至 999 范围内的整数,遵循 Node.js 定义的行为,当状态码不是整数时会返回错误。
app.get('/user', (req, res) => { res.status(99); // Throws an error res.status(200); // OK});res.vary
当缺少field参数时,res.vary会抛出错误。 在 Express 4 中,如果省略该参数,控制台会输出一条警告信息。
app.get('/user', (req, res) => { res.vary(); // Throws an error res.vary('Accept'); // OK});改进
这些变更无需执行任何迁移操作,但在升级时了解这些内容会很有帮助。
res.render()
该方法现在强制所有模板引擎遵循异步行为,避免了因部分模板引擎采用同步实现、违反推荐接口而导致的错误。
Brotli编码支持
除 gzip 和 deflate 外,express.json()、express.urlencoded()、express.text() 和 express.raw() 等中间件现在还支持对传入请求体进行 Brotli(Content-Encoding: br)解压。