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

如何全局处理Mongoose错误?避免在各Schema中重复编写代码

问题

我现在用Mongoose中间件处理验证、类型转换这类Mongoose特定错误,但每个Schema里都要重复写这段代码:

schema.post('save', handleValidationError);
schema.post('findOneAndUpdate', handleValidationError);
schema.post(['findOne', 'deleteOne'], handleCastError);

有没有办法不用重复写,全局给所有Schema配置这些错误处理?我试过写插件,但出错时插件没触发,插件代码如下:

const errorsPlugin = (schema: any, _options: any): void => {
    schema.post('save', handleValidationError);
    schema.post('findOneAndUpdate', handleValidationError);
    schema.post(['findOne', 'deleteOne'], handleCastError);
};

const connection = await mongoConnect(url);
plugin(errorsPlugin);
logger.info(`MongoDB connected: ${connection.connection.name}`);

编辑1:错误处理函数

const handleValidationError = (error: NodeJS.ErrnoException, _res: Response, next: (err?: Error) => void): void => {
    if (error.code?.toString() === '11000') {
        const { keyPattern, keyValue } = error as Error & {
            keyPattern: { [key: string]: number };
            keyValue: { [key: string]: string };
        };
        const key = Object.keys(keyPattern)[0];
        const value = Object.values(keyValue)[0];
        throw new DuplicatedKeyError(key as string, value as string);
    } else if (error instanceof mongooseError.ValidationError) {
        throw new ValidationError(error.errors);
    } else if (error instanceof mongooseError.CastError) {
        throw new CastError(error.kind, error.value);
    }
    next();
};
解决方案

1. 修正全局插件注册方式

要全局注册插件,得调用Mongoose核心模块的plugin方法,不能用连接对象的plugin。正确写法:

import mongoose from 'mongoose';

const errorsPlugin = (schema: any, _options: any): void => {
    schema.post('save', handleValidationError);
    schema.post('findOneAndUpdate', handleValidationError);
    // 针对deleteOne,需指定触发场景(文档级/查询级)
    schema.post(['findOne', 'deleteOne'], { document: true }, handleCastError);
};

// 先注册全局插件,再创建数据库连接
mongoose.plugin(errorsPlugin);

const connection = await mongoConnect(url);
logger.info(`MongoDB connected: ${connection.connection.name}`);

2. 修正错误处理函数的参数顺序

Mongoose的post错误中间件参数顺序是(error, doc, next),你之前把doc当成了Express的res,参数完全错了,这是插件没触发的核心原因。修正后:

import mongoose, { Error as mongooseError } from 'mongoose';

const handleValidationError = (error: any, _doc: any, next: (err?: Error) => void): void => {
    if (error.code?.toString() === '11000') {
        const { keyPattern, keyValue } = error;
        const key = Object.keys(keyPattern)[0];
        const value = Object.values(keyValue)[0];
        throw new DuplicatedKeyError(key, value);
    } else if (error instanceof mongooseError.ValidationError) {
        throw new ValidationError(error.errors);
    } else if (error instanceof mongooseError.CastError) {
        throw new CastError(error.kind, error.value);
    }
    next(error); // 未处理的错误要传递给后续中间件,别吞掉
};

提示:如果要把Mongoose错误转成自定义错误交给Express处理,要确保Express的错误中间件能捕获这些抛出的错误(比如路由里用try/catch包裹,或者依赖Express的全局错误中间件)。

3. 确保findOneAndUpdate触发验证

默认情况下findOneAndUpdate不会运行验证器,必须手动开启runValidators: true选项,要么在调用方法时指定,要么在Schema配置里全局设置:

// 调用示例
await Model.findOneAndUpdate(
  { _id: id },
  updateData,
  { runValidators: true, new: true }
);

4. 适配deleteOne的不同调用场景

如果调用的是文档实例的deleteOne(),文档级中间件会触发;如果是调用Model的deleteOne(),需要注册查询级中间件才能触发:

// 同时支持文档和查询级的deleteOne
schema.post('deleteOne', { document: true, query: true }, handleCastError);

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 13:15:24