Menu

Express 路由与 Router:路由、参数和前缀

Express 中的一个路由由请求方法、路径和处理函数组成:app.get('/users/:id', handler)。把相关的路由放进一个 express.Router(),再用 app.use('/api', router) 挂载到某个前缀下。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

Express 中的路由把一个 HTTP 方法和一个路径映射到一个处理函数:app.get('/users', handler) 响应发往 /users 的 GET 请求,app.post()、app.put() 和 app.delete() 则对其他方法做同样的事。以冒号开头的路径段(如 /users/:id)是路由参数,它的值会放进 req.params。为了让不断变大的应用保持条理,把相关的路由放进一个 express.Router(),再用 app.use('/api', router) 挂载到某个前缀下。

GET /users/2 返回 {"id":2,"name":"Linus"}。路由参数永远是字符串,所以处理函数要和 Number(req.params.id) 比较。发送 POST /users,请求体为 {"name": "Grace"},会得到 201;接着发送 DELETE /users/1 和 GET /users,看看列表的变化。

带前缀的 express.Router()

路由器是一个迷你应用,有自己的路由和中间件。它的路径是相对的:在挂载到 /api/books 的路由器中写成 /:id 的路由,响应的是 /api/books/:id。在真实项目中,每个路由器放在单独的文件里,并以 module.exports = router 结尾。

GET /api/books/1 返回 {"id":1,"title":"Dune"}。router.route('/:id') 只写一次路径,然后为每个方法链式添加一个处理函数;发送 PUT /api/books/1,你会得到 {"updated":"1"}。把挂载路径改成 /v2/books,同一个路由器就会在那里响应。

路由顺序:先匹配的先生效

Express 按注册顺序尝试各个路由,并在第一个发送了响应的路由处停下。参数路由会捕获它所在位置上的任何值,所以注册在它之后的固定路径永远不会被匹配到:

GET /users/new 返回 {"route":"/users/:id","id":"new"}:参数路由先注册,抢先处理了这个请求。把 /users/new 路由移到 /users/:id 之上,再运行一次,就会得到 {"route":"/users/new"}。然后发送 GET /books,进入底部的 404 处理器。

404 处理器

对于没有匹配的请求,Express 会返回它自己的 HTML 页面 Cannot GET /books。API 通常需要返回 JSON,所以要像上面的代码那样,在最后添加一个不带路径的 app.use()。只有当它之前的所有路由都把请求传下去时,它才会运行。处理函数抛出的错误是另一种情况,需要专门的四参数处理器,详见 Express 错误处理。

Express 5 中的路由路径

Express 5 修改了路径语法,而网上大部分代码片段是为 Express 4 写的:

模式Express 4Express 5
任意路径app.get('*', fn)app.get('/*splat', fn)
可选参数/users/:id?/users{/:id}
参数上的正则模式/users/:id(\\d+)不支持;在处理函数中检查值

单独的 * 或参数后面的 ? 会在注册路由时抛出 TypeError,导致应用无法启动。

常见错误

先注册 404 处理器。 不带路径的 app.use() 匹配一切,于是每个请求都会得到 404。把它放在最底部。

忘了前缀会被去掉。 在挂载到 /api 的路由器内部,req.path 不包含 /api。需要完整路径时,使用 req.originalUrl。

把参数和数字比较。 req.params.id === 1 永远为 false,因为 req.params.id 是字符串 '1'。

常见问题

app.use 和 app.get 有什么区别?

app.get('/users', fn) 只对路径恰好是 /users 的 GET 请求运行 fn。app.use('/users', fn) 对所有请求方法、所有以 /users 开头的路径都会运行,所以它被用来挂载路由器和中间件。

如何让 Express 路由区分大小写?

路由默认不区分大小写,所以 /Users 也能匹配 /users。用 app.set('case sensitive routing', true) 关闭这一行为,或者在单个路由器上使用 express.Router({ caseSensitive: true })。

为什么嵌套路由器里的 req.params 是空的?

路由器只能看到它自己路径中的参数。如果它挂载在 /users/:userId/posts,请用 express.Router({ mergeParams: true }) 创建它,这样父路径中的 req.params.userId 才能拿到。

应该把所有路由都写在一个文件里吗?

路由超过几个之后就不应该了。为每种资源创建一个路由器(routes/users.js、routes/orders.js),用 module.exports = router 导出,再在主文件中用 app.use() 逐个挂载。

Coddy 编程语言插图

和 Coddy 一起学编程

开始