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

已安装express-async-errors,为何仍无法捕获Zod错误?

全局错误处理器无法捕获Zod验证错误(Express + Zod)

我正在开发一款API,尝试通过Zod配置全局错误控制器,但始终无法生效。即使已安装并使用express-async-errors,全局错误处理器handleErrors似乎并未被调用。

错误管理文件

import { NextFunction, Request, Response } from "express";
import { ZodError } from "zod";

class AppError extends Error {
  statusCode: number;

  constructor(message: string, statusCode: number = 400) {
    super(message);
    this.statusCode = statusCode;
  }
}

const handleErrors = (err: Error, req: Request, res: Response, next: NextFunction) => {
  console.log('The error handler have been called');
  
  if(err instanceof AppError) {
    return res.status(err.statusCode).json({message: err.message})
  }
  
  if (err instanceof ZodError) {
    return res.status(400).json({ message: err.flatten().fieldErrors, okok:'okok' });
  }  

  return res.status(500).json({message: 'Internal server error'})
}

export { AppError, handleErrors };

Zod Schema定义

import { z } from "zod"

const moviesSchema = z.object({
    id: z.number(),
    name: z.string().max(50),
    description: z.string().optional().nullish(),
    duration: z.number(),
    price: z.number()
})

const moviesRequestSchema = moviesSchema.omit({id: true})

export {moviesRequestSchema, moviesSchema}

验证中间件

import { NextFunction, Request, Response } from "express"
import {  ZodTypeAny } from "zod"

const checkIfCreateDataIsValid = (schema: ZodTypeAny) => (req: Request, res: Response, next: NextFunction) =>  {
    const validatedDate = schema.parse(req.body)
    req.body = validatedDate

    return next()
}

export default checkIfCreateDataIsValid

路由配置

import { Router } from "express";
import { createMoviesController } from "../controllers/movies.controller";
import checkIfCreateDataIsValid from "../middlewares/checkIfCreateDataIsValid.middleware";
import { moviesRequestSchema } from "../schemas/movies.schemas";

const moviesRoutes: Router = Router()

moviesRoutes.post('', checkIfCreateDataIsValid(moviesRequestSchema), createMoviesController)

export default moviesRoutes

app.ts配置

import "reflect-metadata"
import "express-async-errors"
import express, { Application } from "express";
import { handleErrors } from "./errors";
import moviesRoutes from "./routes/movies.routes";

const app: Application = express()

app.use(handleErrors)
app.use(express.json())
app.use('/movies', moviesRoutes)

export default app

错误现象

当我发送带有错误参数(如price传入字符串)的POST请求时,控制台并未打印"The error handler have been called",而是直接抛出原始ZodError:

ZodError: [ { "code": "invalid_type", "expected": "number", "received": "string", "path": [ "price" ], "message": "Expected number, received string" } ] at Object.get error [as error] (C:\Users\arauj\Downloads\projetos\m4-movies-typeorm-H3isenberg8\node_modules\zod\lib\types.js:43:31) at ZodObject.parse (C:\Users\arauj\Downloads\projetos\m4-movies-typeorm-H3isenberg8\node_modules\zod\lib\types.js:141:22) at C:\Users\arauj\Downloads\projetos\m4-movies-typeorm-H3isenberg8\src\middlewares\checkIfCreateDataIsValid.middleware.ts:5:34 at newFn (C:\Users\arauj\Downloads\projetos\m4-movies-typeorm-H3isenberg8\node_modules\express-async-errors\index.js:16:20) at Layer.handle [as handle_request] (C:\Users\arauj\Downloads\projetos\m4-movies-typeorm-H3isenberg8\node_modules\express\lib\router\layer.js:95:5) at next (C:\Users\arauj\Downloads\projetos\m4-movies-typeorm-H3isenberg8\node_modules\express\lib\router\route.js:144:13) at Route.dispatch (C:\Users\arauj\Downloads\projetos\m4-movies-typeorm-H3isenberg8\node_modules\express\lib\router\route.js:114:3) at newFn (C:\Users\arauj\Downloads\projetos\m4-movies-typeorm-H3isenberg8\node_modules\express-async-errors\index.js:16:20) at Layer.handle [as handle_request] (C:\Users\arauj\Downloads\projetos\m4-movies-typeorm-H3isenberg8\node_modules\express\lib\router\layer.js:95:5) at C:\Users\arauj\Downloads\projetos\m4-movies-typeorm-H3isenberg8\node_modules\express\lib\router\index.js:284:15

解决方案

1. 调整全局错误中间件的顺序

Express中间件按注册顺序执行,你当前把handleErrors放在最前面,此时请求还未经过路由和其他中间件,错误无法被捕获。必须将全局错误中间件放在所有路由和其他中间件之后:

修改app.ts:

import "reflect-metadata"
import "express-async-errors"
import express, { Application } from "express";
import { handleErrors } from "./errors";
import moviesRoutes from "./routes/movies.routes";

const app: Application = express()

// 先注册JSON解析中间件
app.use(express.json())
// 再注册业务路由
app.use('/movies', moviesRoutes)
// 最后注册全局错误中间件
app.use(handleErrors)

export default app

2. (可选)用Zod的safeParse手动处理错误

Zod的parse是同步抛出错误,Express本身会捕获同步错误并传递给错误中间件,但顺序正确是前提。如果想更稳妥,可改用safeParse手动传递错误:

修改验证中间件:

import { NextFunction, Request, Response } from "express"
import { ZodError, ZodTypeAny } from "zod"

const checkIfCreateDataIsValid = (schema: ZodTypeAny) => (req: Request, res: Response, next: NextFunction) =>  {
    const result = schema.safeParse(req.body)
    
    if (!result.success) {
        // 将ZodError传递给错误中间件
        return next(result.error)
    }
    
    req.body = result.data
    return next()
}

export default checkIfCreateDataIsValid

3. 修正拼写错误

错误处理器中返回的Intesdarnal server error是拼写错误,建议改为Internal server error,不影响功能但提升规范性。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 04:27:51