编程 Express 5 迁移:app.del 直接抛错、通配符必须命名、async 抛错自动进错误中间件

2026-09-28 00:04:52

Express 5 迁移:app.del 直接抛错、通配符必须命名、async 抛错自动进错误中间件

Express 5 的 API 和 Express 4 基本一致,破坏性变更集中在两类:一类是用了就崩的移除项,一类是不崩但行为变了的地方。迁移前先看官方指南:Upgrade to Express v5。

安装与 codemod

Express 5 需要 Node.js 18 或更高版本。

npm install "express@5"

装完先跑一遍自动化测试,看哪些用例挂了。

codemod 可以批量处理大部分机械改写:

# 全部 recipe
npx codemod@latest @expressjs/v5-migration-recipe

# 指定单个 recipe
npx codemod@latest @expressjs/name-of-the-codemod

下面每一项都标了对应的 codemod 名,可以单独跑。

移除的方法和属性

这些 API 在 Express 5 里已经不存在,调用会让应用直接崩掉。

app.del() 已移除,改用 app.delete()。 当年用 del 是因为 delete 是保留字,ES6 之后已经允许它作为属性名。codemod:@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) 已移除,这个签名从 v4.11.0 起就废弃了。router.param(fn) 同样移除。

复数化的方法名。 codemod:@expressjs/pluralize-method-names。

  • req.acceptsCharset() → req.acceptsCharsets()
  • req.acceptsEncoding() → req.acceptsEncodings()
  • req.acceptsLanguage() → req.acceptsLanguages()

app.param(name, fn) 的 name 带前导冒号会被静默忽略,实际使用去掉前缀后的名字。

req.param(name) 已移除。 这个方法同时从表单、路由、查询串里取值,容易被误用。现在必须明确指定来源,从 req.params、req.body 或 req.query 里取。codemod:@expressjs/explicit-request-params。

res.json(obj, status) / res.jsonp(obj, status) 已移除。

// 旧
res.json(obj, status);
res.jsonp(obj, status);

// 新
res.status(status).json(obj);
res.status(status).jsonp(obj);

res.redirect(url, status) 已移除,参数顺序反过来。 codemod:@expressjs/redirect-arg-order。默认 302 的用法不受影响。

// 旧
res.redirect('/dashboard', 301);

// 新
res.redirect(301, '/dashboard');

// 默认 302,不用改
res.redirect('/dashboard');

res.redirect('back') 和 res.location('back') 的魔术字符串已移除,改成显式读取 Referrer。codemod:@expressjs/back-redirect-deprecated。

// 旧
res.redirect('back');

// 新
res.redirect(req.get('Referrer') || '/');

res.send(body, status) 和 res.send(status) 已移除。

// 旧
res.send({ ok: true }, 201);
res.send(204);

// 新
res.status(201).send({ ok: true });
res.sendStatus(204);

// 要发数字,就当成字符串发
res.send('204');

res.sendfile() 换成驼峰命名的 res.sendFile()。 codemod:@expressjs/camelcase-sendfile。

res.sendFile() 的选项里 hidden 和 from 不再支持,改用 dotfiles 和 root。dotfiles 对隐藏目录同样生效:/var/www/app/.cache/index.html 现在需要 dotfiles: 'allow',即便 index.html 本身不是点文件。Express 4 会默认返回路径里的隐藏目录,Express 5 不显式开启就返回 404。检查只覆盖 send 实际求值的那段路径——传了 root 时,只检查相对 root 的部分。

express.static() 的选项里 hidden 和 from 不再支持,改用 dotfiles 和 root。from 从来没写进文档,只是作为 root 的别名被接受。dotfiles 默认值现在是 "ignore",GET /.well-known/acme-challenge/... 这类请求在 Express 4 默认能返回,Express 5 不带 dotfiles: 'allow' 就是 404。只影响相对 root 的路径部分,root 自身里藏着的目录不受影响。

express.static.mime 不再是导出属性,改用 mime-types 包。

// 旧
express.static.mime.lookup('json');

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

MIME 类型变化。 Express 4 使用 mime-db 1.52.0,Express 5 用的是更新的版本。最明显的一处是 .js 文件现在以 text/javascript 返回,而不是 application/javascript。只影响 express.static() 和 res.sendFile()。MIME 变化不算破坏性变更,可能在小版本或补丁版本之间继续变。

express:router 系列的 debug 日志搬到了独立的 router 依赖命名空间。

版本命名空间
v4express:router、express:router:layer、express:router:route
v5router、router:layer、router:route

Express 5.1 之前这些日志并不存在。

# v4
DEBUG=express:* node index.js

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

行为变更

这些 API 还在,只是行为不一样。

路由 path 匹配语法

改动作用在 app.all、app.use、app.METHOD、router.* 的第一个参数上。

通配符 * 必须命名。 用 /*splat 代替 /*,命名规则和参数 : 一致(参数名本身也可以是合法 JS 标识符,或用 :"this" 这样的引号形式)。注意 *splat 不匹配根路径,要连根路径一起匹配就写 /{*splat}。

// 旧
app.get('/*', ...);

// 新
app.get('/*splat', ...);   // 不匹配根路径
app.get('/{*splat}', ...); // 匹配根路径

可选字符 ? 不再支持,改用花括号。

// 旧
app.get('/:file.:ext?', ...);

// 新
app.get('/:file{.:ext}', ...);

正则字符不再支持。

// 旧
app.get('/[discussion|page]/:slug', ...);

// 新
app.get(['/discussion/:slug', '/page/:slug'], ...);

()[]?+! 这些字符在升级期间被保留,避免歧义,需要用时加 \ 转义。

rejected promise 会转给错误中间件

中间件和路由处理函数返回 rejected promise 时,现在会被当成一个 Error 转发给错误处理中间件。async 处理函数写起来更直接:async 函数里抛出的错误,或 await 了一个 rejected promise,都会像调用了 next(err) 一样进错误处理,参考错误处理文档。

// 旧
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 的 extended 默认值

extended 现在默认为 false。依赖嵌套对象的表单解析要显式打开。

// 旧
app.use(express.urlencoded());

// 新
app.use(express.urlencoded({ extended: true }));

express.static 的 dotfiles 默认值

dotfiles 现在默认 "ignore"。Express 4 默认会返回点文件,Express 5 里以点开头的目录(比如 .well-known)内的文件一律 404。即使之前从没用过 hidden 选项,也可能需要加上这一项。

// 旧
app.use(express.static('public'));

// 新
app.use(express.static('public', { dotfiles: 'allow' }));

改完再跑一遍测试,对照上面的清单逐项核对手工改动的部分。

复制全文 生成海报 Express 风格 Express风格 Node.js 迁移

推荐文章

程序员茄子在线接单