迁移到 Express 5

概述

Express 5 与 Express 4 没有太大区别;尽管它保持了相同的基本 API,但仍然存在一些不兼容先前版本的更改。因此,使用 Express 4 构建的应用程序,在更新到 Express 5 后可能无法正常工作。

要安装此版本,您需要 Node.js 18 或更高版本。然后,在您的应用程序目录中执行以下命令

npm install "express@5"

然后,您可以运行自动化测试以查看哪些失败,并根据下面列出的更新修复问题。解决测试失败后,运行您的应用程序以查看发生哪些错误。您将立即发现应用程序是否使用了任何不受支持的方法或属性。

Express 5 Codemods

为了帮助您迁移 Express 服务器,我们创建了一套 codemods,可以帮助您自动将代码更新到最新版本的 Express。

运行以下命令以运行所有可用的 codemods

npx @expressjs/codemod upgrade

如果您想运行特定的 codemod,可以运行以下命令

npx @expressjs/codemod name-of-the-codemod

您可以在此处找到可用 codemods 的列表。

Express 5 中的变更

已移除的方法和属性

已更改

改进

已移除的方法和属性

如果您的应用程序使用了这些方法或属性中的任何一个,它将会崩溃。因此,在更新到版本 5 后,您需要更改您的应用程序。

app.del()

Express 5 不再支持 app.del() 函数。如果您使用此函数,将抛出错误。要注册 HTTP DELETE 路由,请改用 app.delete() 函数。

最初,使用 del 而不是 delete,因为 delete 是 JavaScript 中的保留关键字。然而,从 ECMAScript 6 开始,delete 和其他保留关键字可以合法地用作属性名。

注意

您可以使用以下命令替换已弃用的签名

npx @expressjs/codemod v4-deprecated-signatures
// v4
app.del('/user/:id', (req, res) => {
  res.send(`DELETE /user/${req.params.id}`)
})

// v5
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.acceptsCharsets() 替换。

req.acceptsEncoding() 已被 req.acceptsEncodings() 替换。

req.acceptsLanguage() 已被 req.acceptsLanguages() 替换。

注意

您可以使用以下命令替换已弃用的签名

npx @expressjs/codemod pluralized-methods
// v4
app.all('/', (req, res) => {
  req.acceptsCharset('utf-8')
  req.acceptsEncoding('br')
  req.acceptsLanguage('en')

  // ...
})

// v5
app.all('/', (req, res) => {
  req.acceptsCharsets('utf-8')
  req.acceptsEncodings('br')
  req.acceptsLanguages('en')

  // ...
})

app.param(name, fn) 中 name 的前导冒号 (:)

app.param(name, fn) 函数中 name 参数的前导冒号字符 (:) 是 Express 3 的遗留问题,为了向后兼容,Express 4 支持它并带有弃用通知。Express 5 将默默地忽略它,并使用不带冒号前缀的 name 参数。

如果您遵循 app.param 的 Express 4 文档,这不应该影响您的代码,因为它没有提及前导冒号。

req.param(name)

这种可能令人困惑且危险的检索表单数据的方法已被移除。您现在需要特意在 req.paramsreq.bodyreq.query 对象中查找提交的参数名。

注意

您可以使用以下命令替换已弃用的签名

npx @expressjs/codemod req-param
// v4
app.post('/user', (req, res) => {
  const id = req.param('id')
  const body = req.param('body')
  const query = req.param('query')

  // ...
})

// v5
app.post('/user', (req, res) => {
  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 @expressjs/codemod v4-deprecated-signatures
// v4
app.post('/user', (req, res) => {
  res.json({ name: 'Ruben' }, 201)
})

// v5
app.post('/user', (req, res) => {
  res.status(201).json({ name: 'Ruben' })
})

res.jsonp(obj, status)

Express 5 不再支持 res.jsonp(obj, status) 签名。相反,请先设置状态,然后将其链接到 res.jsonp() 方法,例如:res.status(status).jsonp(obj)

注意

您可以使用以下命令替换已弃用的签名

npx @expressjs/codemod v4-deprecated-signatures
// v4
app.post('/user', (req, res) => {
  res.jsonp({ name: 'Ruben' }, 201)
})

// v5
app.post('/user', (req, res) => {
  res.status(201).jsonp({ name: 'Ruben' })
})

res.redirect(url, status)

Express 5 不再支持 res.redirect(url, status) 签名。请改用以下签名:res.redirect(status, url)

注意

您可以使用以下命令替换已弃用的签名

npx @expressjs/codemod v4-deprecated-signatures
// v4
app.get('/user', (req, res) => {
  res.redirect('/users', 301)
})

// v5
app.get('/user', (req, res) => {
  res.redirect(301, '/users')
})

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 @expressjs/codemod magic-redirect
// v4
app.get('/user', (req, res) => {
  res.redirect('back')
})

// v5
app.get('/user', (req, res) => {
  res.redirect(req.get('Referrer') || '/')
})

res.send(body, status)

Express 5 不再支持 res.send(obj, status) 签名。相反,请先设置状态,然后将其链接到 res.send() 方法,例如:res.status(status).send(obj)

注意

您可以使用以下命令替换已弃用的签名

npx @expressjs/codemod v4-deprecated-signatures
// v4
app.get('/user', (req, res) => {
  res.send({ name: 'Ruben' }, 200)
})

// v5
app.get('/user', (req, res) => {
  res.status(200).send({ name: 'Ruben' })
})

res.send(status)

Express 5 不再支持 res.send(status) 签名,其中 status 是一个数字。请改用 res.sendStatus(statusCode) 函数,该函数设置 HTTP 响应头状态码并发送代码的文本版本,例如:“Not Found”、“Internal Server Error”等。如果您需要使用 res.send() 函数发送数字,请将数字用引号括起来以将其转换为字符串,这样 Express 就不会将其解释为尝试使用不受支持的旧签名。

注意

您可以使用以下命令替换已弃用的签名

npx @expressjs/codemod v4-deprecated-signatures
// v4
app.get('/user', (req, res) => {
  res.send(200)
})

// v5
app.get('/user', (req, res) => {
  res.sendStatus(200)
})

res.sendfile()

res.sendfile() 函数在 Express 5 中已被驼峰命名法版本 res.sendFile() 替换。

注意

您可以使用以下命令替换已弃用的签名

npx @expressjs/codemod v4-deprecated-signatures
// v4
app.get('/user', (req, res) => {
  res.sendfile('/path/to/file')
})

// v5
app.get('/user', (req, res) => {
  res.sendFile('/path/to/file')
})

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 类型值。

// v4
express.static.mime.lookup('json')

// v5
const mime = require('mime-types')
mime.lookup('json')

express:router 调试日志

在 Express 5 中,路由器处理逻辑由一个依赖项执行。因此,路由器的调试日志不再在 express: 命名空间下可用。在 v4 中,日志在 express:routerexpress:router:layerexpress:router:route 命名空间下可用。所有这些都包含在 express:* 命名空间下。在 v5.1+ 中,日志在 routerrouter:layerrouter:route 命名空间下可用。router:layerrouter:route 的日志包含在 router:* 命名空间中。为了在使用 v4 中的 express:* 时获得相同的详细调试日志,请结合使用 express:*routerrouter:*

# v4
DEBUG=express:* node index.js

# v5
DEBUG=express:*,router,router:* node index.js

已更改

路径路由匹配语法

路径路由匹配语法是指将字符串作为第一个参数提供给 app.all()app.use()app.METHOD()router.all()router.METHOD()router.use() API。对路径字符串如何与传入请求匹配进行了以下更改

// v4
app.get('/*', async (req, res) => {
  res.send('ok')
})

// v5
app.get('/*splat', async (req, res) => {
  res.send('ok')
})

注意

*splat 匹配不带根路径的任何路径。如果您还需要匹配根路径 /,可以使用 /{*splat},将通配符括在花括号中。

// v5
app.get('/{*splat}', async (req, res) => {
  res.send('ok')
})
// v4
app.get('/:file.:ext?', async (req, res) => {
  res.send('ok')
})

// v5
app.get('/:file{.:ext}', async (req, res) => {
  res.send('ok')
})

中间件和处理程序处理被拒绝的 Promise

返回被拒绝 Promise 的请求中间件和处理程序现在通过将被拒绝的值作为 Error 转发给错误处理中间件来处理。这意味着将 async 函数用作中间件和处理程序比以往任何时候都更容易。当在 async 函数中抛出错误或在 async 函数内部 await 被拒绝的 Promise 时,这些错误将传递给错误处理程序,就像调用 next(err) 一样。

Express 如何处理错误的详细信息,请参阅错误处理文档

express.urlencoded

express.urlencoded 方法默认将 extended 选项设为 false

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 中,它默认返回 {}

req.host

在 Express 4 中,req.host 函数错误地去掉了存在的端口号。在 Express 5 中,端口号得以保留。

req.query

req.query 属性不再是可写属性,而是一个 getter。默认的查询解析器已从“extended”更改为“simple”。

res.clearCookie

res.clearCookie 方法忽略用户提供的 maxAgeexpires 选项。

res.status

res.status 方法只接受 100999 范围内的整数,遵循 Node.js 定义的行为,并且当状态码不是整数时会返回错误。

res.vary

当缺少 field 参数时,res.vary 会抛出错误。在 Express 4 中,如果省略该参数,则会在控制台中给出警告

改进

res.render()

此方法现在强制所有视图引擎采用异步行为,从而避免了由于视图引擎的同步实现而导致的错误,这些实现违反了推荐的接口。

Brotli 编码支持

Express 5 支持 Brotli 编码,用于处理支持它的客户端发出的请求。

编辑此页面