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

如何在TypeScript与Express中正确为请求参数添加类型?

在Express控制器中用Zod生成的类型最优标注方式

问题描述

我正在使用Zod验证传入请求,并借助其infer功能生成请求体等对应的类型。现在想了解,在Express控制器中如何最优地指定这些类型?

假设接口需要如下请求体结构:

{ "name": string, "address": string }

我已通过Zod Schema生成了UserRequestBody类型,希望在控制器中用该类型为请求添加类型标注。我通过Express的RequestHandler接口实现了需求,代码如下:

const newUser: RequestHandler<unknown, unknown, UserRequestBody, unknown> = async (req, res, next) => {
  console.log(req.body.name)
}

虽然能获得智能提示,但我想知道这是否是最优方案,是否存在更巧妙的请求类型标注方式?


最优方案与简化方式

你当前的写法是可行的,但可以通过以下几种方式让代码更简洁、更贴合实际开发场景:

1. 直接给req参数标注类型

无需通过RequestHandler约束整个函数类型,直接利用Express的Request泛型给req指定类型,更灵活直观:

import { Request, Response, NextFunction } from 'express';

async function newUser(req: Request<unknown, unknown, UserRequestBody>, res: Response, next: NextFunction) {
  console.log(req.body.name); // 自动获得类型提示
}

这种方式只聚焦需要约束的req.body,不需要写冗余的unknown参数。

2. 封装自定义类型别名减少重复

如果多个控制器都需要类似的泛型组合,可以封装一个类型别名,避免重复书写unknown:

import { Request } from 'express';

type AppRequest<TBody = unknown> = Request<unknown, unknown, TBody>;

async function newUser(req: AppRequest<UserRequestBody>, res: Response, next: NextFunction) {
  console.log(req.body.name);
}

后续其他控制器只需传入请求体类型即可复用,代码更简洁。

3. 从Zod验证结果自动推导类型

如果在控制器内直接执行Zod验证,可以跳过手动导入UserRequestBody,直接从parse的返回值推导类型:

import { z } from 'zod';
import { Request, Response, NextFunction } from 'express';

const userSchema = z.object({
  name: z.string(),
  address: z.string()
});

async function newUser(req: Request, res: Response, next: NextFunction) {
  const validatedBody = userSchema.parse(req.body);
  console.log(validatedBody.name); // validatedBody自动匹配UserRequestBody类型
}

这种方式让类型与验证逻辑强绑定,避免类型和Schema不一致的问题。

4. 用中间件统一处理验证与类型注入

更优雅的方案是编写Zod验证中间件,在中间件中完成请求体验证,并将验证后的类型注入到req中:

import { z } from 'zod';
import { Request, Response, NextFunction } from 'express';

const validateBody = <T extends z.ZodTypeAny>(schema: T) => {
  return (req: Request, res: Response, next: NextFunction) => {
    req.body = schema.parse(req.body);
    next();
  };
};

// 定义Schema
const userSchema = z.object({
  name: z.string(),
  address: z.string()
});

// 控制器中直接使用推导后的类型
async function newUser(req: Request<unknown, unknown, z.infer<typeof userSchema>>, res: Response) {
  console.log(req.body.name); // 类型自动匹配
}

// 路由使用中间件
app.post('/users', validateBody(userSchema), newUser);

这种方式将验证逻辑与控制器解耦,确保进入控制器的req.body已经是验证通过的合法类型,兼顾类型安全和代码整洁。

总结

  • 简单场景:直接给req标注Request<unknown, unknown, UserRequestBody>是最直接的简化方式;
  • 多接口复用:封装自定义类型别名减少重复代码;
  • 强类型绑定:从Zod验证结果推导类型或使用验证中间件,是更健壮的方案。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 07:52:09