自定义Express路由中PUT请求req.params.id返回undefined问题排查
你的实现存在两处核心问题,直接导致req.params.id始终为undefined:
1. AdminRouter 未利用 Express 原生路由解析机制
你当前的adminRouter是通过手动判断req.path.startsWith来分发请求的,这种方式完全绕开了Express的路由参数解析逻辑。Express只有在通过router.get()/router.put()等方法注册带参数的路由(如/update/:id)时,才会自动解析URL中的参数并填充到req.params对象里。
你的adminRouter只是一个普通中间件函数,没有告诉Express需要解析:id这类参数,所以req.params自然是空的。
2. 自定义路由匹配逻辑未提取参数
即使你在electionEndpointHandler里用matchRoutes匹配了/elections/update/:id,但这个自定义匹配逻辑并没有把提取到的id值注入到req.params中,而是直接使用了未被填充的原始req.params。
修复方案(推荐使用标准Express Router)
放弃手动判断路径的方式,改用Express原生Router来定义路由,这是最可靠且符合框架设计的方案:
第一步:重构makeAdminAPIRouter
用express.Router()创建路由实例,通过标准方法注册子路由:
import express from 'express'; import { Request, Response, NextFunction } from 'express'; function makeAdminAPIRouter({ adminAuthController, electionController, }: Controllers) { const adminRouter = express.Router(); // 把auth相关请求分发到adminAuthController对应的路由 adminRouter.use('/auth', adminAuthController); // 把elections相关请求分发到electionController对应的路由 adminRouter.use('/elections', electionController); return adminRouter; } export const adminRouter = makeAdminAPIRouter({ adminAuthController, electionController, });
第二步:重构electionController为Router实例
让electionController也成为一个Router,注册具体的带参数路由:
function makeElectionController({ electionService }: Dep) { const router = express.Router(); // 处理POST /elections请求 router.post('/', async (req, res, next) => { try { const result = await electionService.Create({ body: req.body, params: req.params, query: req.query, }); bindRequestResponse(result)(req, res); } catch (err) { next(err); } }); // 处理PUT /elections/update/:id请求,Express会自动解析id到req.params router.put('/update/:id', async (req, res, next) => { try { const result = await electionService.Update({ body: req.body, params: req.params, // 这里req.params.id已经有值了 query: req.query, }); bindRequestResponse(result)(req, res); } catch (err) { next(err); } }); return router; }
备选方案(保留自定义路由匹配)
如果一定要维持自定义的matchRoutes逻辑,需要手动解析路由参数并填充到req.params中,可以使用path-to-regexp库(Express内部也是用这个库解析路由的):
import { match } from 'path-to-regexp'; function makeAdminAPIRouter({ adminAuthController, electionController, }: Controllers) { return function route(req: Request, res: Response, next: NextFunction) { // 匹配auth路由并提取参数 const authMatcher = match('/auth/:rest*', { decode: decodeURIComponent }); const authMatchResult = authMatcher(req.path); if (authMatchResult) { req.params = { ...req.params, ...authMatchResult.params }; return adminAuthController(req, res, next); } // 匹配elections路由并提取参数 const electionMatcher = match('/elections/update/:id', { decode: decodeURIComponent }); const electionMatchResult = electionMatcher(req.path); if (electionMatchResult) { req.params = { ...req.params, ...electionMatchResult.params }; return electionController(req, res, next); } next(); }; }
但这种方式需要手动维护所有路由的匹配规则,容易出错,远不如使用Express原生Router高效可靠。
内容的提问来源于stack exchange,提问作者RaqDeku

