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

如何修复接口无法返回JSON格式ZodError校验错误的问题

问题根因

核心错误是Zod schema 解析逻辑写在了try/catch块外部:

  • const parsedData = await payloadSchema.parseAsync(req.body); 这行没有被错误捕获逻辑包裹,只要入参不符合schema规则,parseAsync会直接抛出ZodError,这个错误不会进入你后续编写的catch分支
  • 未被捕获的异步错误会直接中断当前请求的处理流程,服务端不会向客户端返回任何响应,因此Insomnia会提示「Error: Couldn't connect to server」,错误堆栈仅打印在服务终端。
修改方案

两种改法可根据编码习惯二选一:

方案1:将Zod解析逻辑移入try块内(适配现有错误处理逻辑)

直接把解析代码挪到try块最开头,让Zod抛出的错误能被catch正常捕获,即可返回预期的JSON格式错误,修改后完整代码参考:

// 必须先引入ZodError,否则catch中的类型判断会失效
import { ZodError } from "zod";

export const register = async (req: Request, res: Response) => {
  const payloadSchema = z
    .object({
      firstname: z.string({
        required_error: "Firstname is required",
        invalid_type_error: "Title must be a string",
      }),
      lastname: z.string({
        required_error: "Lastname is required",
        invalid_type_error: "Title must be a string",
      }),
      email: z
        .string({ required_error: "Email is required" })
        .email({ message: "Invalid email address" }),
      password: z.string(),
      confirm: z.string(),
    })
    .refine((data) => data.password === data.confirm, {
      message: "Passwords don't match",
      path: ["confirm"], 
    });

  try {
    // Zod解析移入try块内部
    const parsedData = await payloadSchema.parseAsync(req.body);

    const result = await User.findOne({ where: { email: parsedData.email } });

    if (result) {
      return res.status(400).json({
        success: false,
        error: "User already exists",
      });
    }

    const user = new User();
    user.firstname = parsedData.firstname;
    user.lastname = parsedData.lastname;
    user.email = parsedData.email;
    user.password = parsedData.password;
    await user.save();

    const accessToken = jwt.sign(
      { userId: user.id },
      process.env.TOKEN_SECRET
    );

    return res.status(200).json({
      success: true,
      createdUser: user,
      accessToken: accessToken,
    });
  } catch (e) {
    if (e instanceof ZodError) {
      return res.status(400).json({
        success: false,
        error: e.flatten(),
      });
    } else if (e instanceof Error) {
      return res.status(400).json({
        success: false,
        message: e.message,
      });
    }
    // 增加未知错误兜底返回
    return res.status(500).json({
      success: false,
      message: "Internal server error"
    })
  }
};

方案2:使用Zod自带的safeParseAsync方法(不依赖try/catch捕获校验错误)

如果想把参数校验逻辑和业务错误处理逻辑拆分,可以用Zod提供的不抛错的安全解析方法,直接判断解析结果返回即可:

// 直接解析,不会抛出异常
const parseResult = await payloadSchema.safeParseAsync(req.body);
if (!parseResult.success) {
  return res.status(400).json({
    success: false,
    error: parseResult.error.flatten()
  })
}
// 解析成功后,合法数据存在parseResult.data中,后续业务逻辑直接取用
const parsedData = parseResult.data;

这种写法逻辑更清晰,参数校验错误不会和后续数据库操作、JWT生成等业务错误混在同一个catch块中,排查问题更高效。

额外注意点
  • 必须在文件顶部引入ZodError,否则catch中e instanceof ZodError的判断永远不成立,Zod错误会走到普通Error的分支,返回格式不符合预期
  • catch块必须加未知错误的兜底返回,避免出现其他未预判的错误时,服务再次出现无响应的问题
  • 不推荐process!.env!.TOKEN_SECRET!这种多层非空断言写法,建议在服务启动时就统一校验必填环境变量,避免运行时出现取值为undefined的问题

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 03:39:35