如何全局处理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
相关产品推荐
相关产品推荐

