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

如何通过TypeScript声明合并为Express Request添加泛型payload属性?

解决方案

要实现路由中req.payload的类型自动推断,需要通过泛型扩展Express Request类型 + 让验证中间件返回带泛型的RequestHandler来解决,具体步骤如下:

1. 扩展Express的Request类型

在项目根目录创建或修改类型声明文件(比如src/types/express.d.ts),给Express的Request接口新增一个泛型参数,用于指定payload的类型:

import { core } from 'express-serve-static-core';

declare module 'express-serve-static-core' {
  interface Request<
    P = core.ParamsDictionary,
    ResBody = any,
    ReqBody = any,
    ReqQuery = core.Query,
    Locals extends Record<string, any> = Record<string, any>,
    Payload = unknown // 新增泛型参数,默认unknown
  > {
    payload?: Payload;
  }
}

注意:直接扩展express-serve-static-core里的Request接口更准确,因为Express的核心类型定义在这个子模块中。

2. 修改验证中间件的类型定义

调整validateRequest的返回类型,让它返回携带Zod推断类型的RequestHandler,这样TypeScript就能把Schema对应的类型绑定到req.payload上:

import { RequestHandler } from 'express';
import { ZodTypeAny, z } from 'zod';
import { fromZodError } from 'zod-validation-error';
import { BadRequest } from './errors'; // 替换成你实际的错误类

export const validateRequest = <T extends ZodTypeAny>(schema: T): RequestHandler<any, any, any, any, any, z.infer<T>> =>
  async (req, res, next) => {
    try {
      // 修正:原代码的`spa`应为`safeParseAsync`(Zod的异步安全解析方法)
      const validationResult = await schema.safeParseAsync(req.body);

      if (!validationResult.success) {
        throw new BadRequest(fromZodError(validationResult.error).toString());
      }

      req.payload = validationResult.data;
      next();
    } catch (error: unknown) {
      next(error);
    }
  };

3. 在路由中使用

现在当你在路由里调用validateRequest并传入对应的Schema时,TypeScript会自动推断req.payload的具体类型:

import { Router } from 'express';
import { validateRequest } from './middleware/validateRequest';
import { createUserSchema, loginUserSchema } from './schemas/user';

const router = Router();

// 注册路由:req.payload自动推断为CreateUserRequest类型
router.post('/register', validateRequest(createUserSchema), (req, res) => {
  // 这里可以直接访问payload的字段,且有类型提示
  const { firstname, lastname, email, pass, passConfirm } = req.payload!;
  
  // 执行用户创建逻辑...
  res.status(201).json({ message: '用户创建成功' });
});

// 登录路由:req.payload自动推断为LoginUserRequest类型
router.post('/login', validateRequest(loginUserSchema), (req, res) => {
  const { email, pass } = req.payload!;
  
  // 执行登录验证逻辑...
  res.json({ token: '生成的JWT Token' });
});

关键说明

  • 为什么之前的方法失效?
    之前的声明合并要么用固定类型(any/联合类型),要么没有利用泛型的动态绑定能力,导致TypeScript无法根据不同的中间件实例推断payload的具体类型。
  • 泛型参数的作用:
    通过给Request新增Payload泛型参数,并让validateRequest返回绑定了该泛型的RequestHandler,实现了“中间件传入什么Schema,路由里的payload就是对应类型”的自动推断。

内容的提问来源于stack exchange,提问作者MMMorpheus

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 02:33:33