You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 03:43:35