如何修复接口无法返回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
相关产品推荐
相关产品推荐

