Node.js Fastify框架中Zod校验错误无法返回至请求响应的问题
Fastify中Zod校验错误无法返回响应的问题
我在使用Node.js的Fastify框架时遇到错误处理问题:Zod校验失败时,错误仅在终端输出,无法作为请求响应返回。在CreateUserController中执行Zod校验时,若请求数据不符合规则,错误只会出现在服务器终端,即使配置了错误处理中间件也无法捕获;但将相同的校验逻辑直接放在路由中时,却能正常返回错误。
相关代码示例
控制器代码
import { CreateUserService } from "./CreateUserService"; import z, { ZodError } from "zod"; import { FastifyReply, FastifyRequest } from "fastify"; class CreateUserController { constructor(private createUser: CreateUserService){} async handle(request: FastifyRequest, reply : FastifyReply){ const schema = z.object({ name: z.string(), address: z.string(), cellNumber: z.string().min(8) }) const {name, address, cellNumber} =schema.parse(request.body) const user = await this.createUser.execute({name,address,cellNumber}); return reply.send(user) } } export {CreateUserController};
路由代码
import { FastifyInstance } from "fastify"; import { createUserFactory } from "../modules/createUser/CreateUSerFactory"; import {z} from 'zod' export async function router (app:FastifyInstance){ app.get("/", (request,reply)=>{ return reply.send("OK") }) app.post('/user',(request,reply)=>{ const schema = z.object({ name: z.string(), address: z.string(), cellNumber: z.string().min(8) }) const {name, address, cellNumber} =schema.parse(request.body) // createUserFactory().handle(request,reply); }) }
问题原因
当在控制器的handle方法里调用schema.parse()时,Zod会直接抛出ZodError,但这个错误没有被Fastify的错误处理机制捕获——控制器的异步方法抛出的错误,需要被Fastify感知到才能转化为响应,而当前代码既没有手动捕获错误,也没有让Fastify处理这个未捕获的异常。
而直接在路由handler里使用schema.parse()时,Fastify会自动捕获路由函数中抛出的同步/异步错误,并将其转换为标准HTTP响应返回给客户端。
解决方案
方案1:控制器内手动捕获ZodError
用try/catch包裹校验逻辑,捕获到ZodError后通过reply主动返回错误响应:
import { CreateUserService } from "./CreateUserService"; import z, { ZodError } from "zod"; import { FastifyReply, FastifyRequest } from "fastify"; class CreateUserController { constructor(private createUser: CreateUserService){} async handle(request: FastifyRequest, reply : FastifyReply){ const schema = z.object({ name: z.string(), address: z.string(), cellNumber: z.string().min(8) }); try { const {name, address, cellNumber} = schema.parse(request.body); const user = await this.createUser.execute({name,address,cellNumber}); return reply.send(user); } catch (error) { if (error instanceof ZodError) { return reply.status(400).send({ statusCode: 400, error: "Bad Request", message: error.errors.map(e => `${e.path.join('.')}: ${e.message}`) }); } // 处理其他未知错误 return reply.status(500).send({ statusCode: 500, error: "Internal Server Error" }); } } } export {CreateUserController};
方案2:使用Zod的safeParse替代parse
safeParse不会直接抛出错误,而是返回包含校验结果的对象,可根据结果分支处理:
import { CreateUserService } from "./CreateUserService"; import z from "zod"; import { FastifyReply, FastifyRequest } from "fastify"; class CreateUserController { constructor(private createUser: CreateUserService){} async handle(request: FastifyRequest, reply : FastifyReply){ const schema = z.object({ name: z.string(), address: z.string(), cellNumber: z.string().min(8) }); const result = schema.safeParse(request.body); if (!result.success) { return reply.status(400).send({ statusCode: 400, error: "Bad Request", message: result.error.errors.map(e => `${e.path.join('.')}: ${e.message}`) }); } const {name, address, cellNumber} = result.data; const user = await this.createUser.execute({name,address,cellNumber}); return reply.send(user); } } export {CreateUserController};
方案3:利用Fastify原生请求校验(推荐)
Fastify原生支持请求参数校验,将Zod规则定义在路由的schema配置中,Fastify会自动完成校验并返回错误响应:
修改路由代码
import { FastifyInstance } from "fastify"; import { createUserFactory } from "../modules/createUser/CreateUSerFactory"; import {z} from 'zod' export async function router (app:FastifyInstance){ app.get("/", (request,reply)=>{ return reply.send("OK") }) app.post('/user', { schema: { body: z.object({ name: z.string(), address: z.string(), cellNumber: z.string().min(8) }) } }, async (request,reply)=>{ // request.body已被Fastify自动校验,可直接使用 await createUserFactory().handle(request,reply); }) }
修改控制器代码(移除内部校验逻辑)
import { CreateUserService } from "./CreateUserService"; import { FastifyReply, FastifyRequest } from "fastify"; class CreateUserController { constructor(private createUser: CreateUserService){} async handle(request: FastifyRequest, reply : FastifyReply){ // 直接使用已校验的请求体 const {name, address, cellNumber} = request.body as { name: string; address: string; cellNumber: string; }; const user = await this.createUser.execute({name,address,cellNumber}); return reply.send(user); } } export {CreateUserController};
内容的提问来源于stack exchange,提问作者Bruno Sampayo
相关产品推荐
相关产品推荐

