Express.js嵌套路由返回404 Not Found问题求助
开发REST API时,配置Express嵌套路由后,请求/accounts/:account-id/cashflows这类URL会返回404 Not Found错误,但当account-id参数为空时(比如请求/accounts//cashflows)却不会报错。使用的Express版本为^4.19.2。
路由入口文件(src/routes/index.ts)
import { Router } from 'express'; import accountRouter from './accounts.js'; import cashflowRouter from './cashflows.js'; const api = () => { const router = Router(); // cashflows <- 404 router.use('/accounts/:account-id/cashflows', cashflowRouter()); // accounts <- 正常工作 router.use('/accounts', accountRouter()); return router; } export default api;
现金流路由文件(src/routes/cashflows.ts)
import { Router } from 'express'; import cashflows from '../controllers/cashflows.js'; const api = () => { const router = Router(); // GET /accounts/:account-id/cashflows <- 获取所有现金流 router.get('/', cashflows.find); // GET /accounts/:account-id/cashflows/:id <- 获取单个现金流 router.get('/:id', cashflows.findOne); // POST /accounts/:account-id/cashflows <- 添加现金流 router.post('/', cashflows.add); // PATCH /accounts/:account-id/cashflows/:id <- 更新现金流 router.patch('/:id', cashflows.update); // DELETE /accounts/:account-id/cashflows/:id <- 删除现金流 router.delete('/:id', cashflows.remove); return router; } export default api;
已尝试的方案
- 将
cashflowRouter作为accountRouter的中间件,并设置mergeParams: true,无效。 - 直接在
index.ts中定义嵌套路由(如下)可以正常工作,但不符合预期的模块化实现:
router.get('/accounts/:id/example', (req, res) => { res.json(req.params.id); })
该测试路由放置在所有其他路由之前。
问题原因
Express的路由参数命名不允许包含连字符(-),仅支持字母、数字和下划线。/:account-id这种带连字符的参数定义会导致Express无法正确解析路由规则,进而无法匹配请求,最终请求会落到后面的/accounts路由,而accountRouter中没有对应/cashflows的处理逻辑,因此返回404。
当account-id参数为空时,URL变为/accounts//cashflows,Express会将连续的斜杠视为单个斜杠,此时请求路径相当于/accounts/cashflows,由于原路由规则的解析问题,反而触发了非预期的匹配(并非正确逻辑)。
解决方案
1. 修正路由参数命名
将路由参数中的连字符改为下划线或驼峰命名,例如account_id或accountId:
修改index.ts中的路由挂载代码:
router.use('/accounts/:accountId/cashflows', cashflowRouter());
2. 开启参数合并(如需在子路由访问父参数)
如果需要在cashflowRouter中访问accountId参数,需要在创建子路由时设置mergeParams: true:
修改cashflows.ts中的路由创建代码:
const router = Router({ mergeParams: true });
这样在cashflows控制器中就能通过req.params.accountId获取到父路由的参数。
3. 确认路由挂载顺序
确保嵌套路由的挂载顺序在基础路由之前(当前代码已符合要求),避免基础路由先匹配请求导致嵌套路由无法生效。
内容的提问来源于stack exchange,提问作者Kamil

