如何用TypeScript实现Express中间件扩展RequestHandlerParams类型?
解决方案:TypeScript下实现类型安全的Express注入中间件
完全可行,TypeScript的泛型和类型扩展可以完美满足你的需求,不用纠结,直接按下面的方式实现即可。
核心思路
利用泛型保留原请求的参数、请求体等类型,通过类型交集(&)扩展Request对象,让中间件和后续处理函数都能获得完整的类型提示。
1. 定义扩展后的Request类型
首先创建一个泛型类型,用来表示注入team后的Request:
import { Request } from 'express'; import { Prisma } from '@prisma/client'; // 泛型类型,保留原Request的Params、Query、Body等类型,同时注入team属性 type RequestWithTeam<P = {}, ResBody = any, ReqBody = any, ReqQuery = any> = Request<P, ResBody, ReqBody, ReqQuery> & { team: Prisma.TeamGetPayload<{ include: Prisma.TeamInclude }>; };
这里用Prisma.TeamGetPayload精准推导team的类型,避免手动定义重复类型。
2. 改造injectTeam中间件(标准Express中间件写法)
如果沿用原来中间件作为路由独立参数的写法,给injectTeam加上泛型适配任意Request参数类型:
import { RequestHandler, NextFunction, Response } from 'express'; import prisma from './prisma-client'; // 你的Prisma客户端实例 const injectTeam = <P extends {}>(include: Prisma.TeamInclude): RequestHandler<P> => { return async (req: Request<P>, res: Response, next: NextFunction) => { // req.params自动推断对应路由的参数类型,比如{ team_uuid: string } const team = await prisma.team.findUnique({ where: { id: req.params.team_uuid }, include, }); if (!team) { return res.status(404).json({ message: 'Team not found' }); } // 类型转换为扩展后的Request类型 (req as RequestWithTeam<P>).team = team; next(); }; };
3. 路由中使用(类型自动提示)
不管是单参数还是多参数路由,TypeScript都会自动识别req.params的类型,同时req.team也会有正确的类型提示:
import { Router } from 'express'; const router = Router(); // 单参数路由示例 router.put('/:team_uuid', injectTeam({ members: true }), async (req: RequestWithTeam<{ team_uuid: string }>, res) => { // req.params.team_uuid 类型为string,自动提示 // req.team 包含members属性,类型正确 console.log(req.team.members); res.json({ success: true }); }); // 多参数路由示例 router.get('/:team_uuid/:member_uuid', injectTeam({ members: { select: { id: true, name: true } } }), async (req: RequestWithTeam<{ team_uuid: string; member_uuid: string }>, res) => { // req.params.team_uuid和member_uuid都有正确类型 // req.team.members的类型是{ id: string; name: string }[] const targetMember = req.team.members.find(m => m.id === req.params.member_uuid); res.json(targetMember); });
4. 实现你提到的“将处理函数传入中间件”的写法
如果想把路由处理函数直接传入injectTeam,用泛型约束处理函数的参数类型:
const injectTeam = <P extends {}>( include: Prisma.TeamInclude, handler: (req: RequestWithTeam<P>, res: Response, next: NextFunction) => Promise<void> | void ): RequestHandler<P> => { return async (req: Request<P>, res: Response, next: NextFunction) => { const team = await prisma.team.findUnique({ where: { id: req.params.team_uuid }, include, }); if (!team) { return res.status(404).json({ message: 'Team not found' }); } // 直接调用传入的处理函数,req已被扩展 await handler(req as RequestWithTeam<P>, res, next); }; }; // 使用示例 router.put('/:team_uuid', injectTeam({ members: true }, async (req, res) => { // req自动推断为RequestWithTeam<{ team_uuid: string }> // req.params.team_uuid 和 req.team 都有正确的类型提示 res.json({ team: req.team }); }));
关键注意点
- 确保
@types/express包是最新版本,避免类型兼容问题。 - 使用
Prisma.TeamGetPayload能让team的类型完全匹配你传入的include参数,比手动定义更准确。 - 如果需要全局扩展Request类型(比如多个中间件都注入属性),可以在
@types/express/index.d.ts中声明全局扩展,但泛型方式更灵活,适合场景化的属性注入。
内容的提问来源于stack exchange,提问作者Architek
相关产品推荐
相关产品推荐

