【问题标题】:How to handle mongoose error with nestjs如何使用nestjs处理猫鼬错误
【发布时间】:2018-11-24 14:46:53
【问题描述】:

我按照https://docs.nestjs.com/techniques/mongodb的例子

问题是当出现猫鼬验证错误时(例如,我有一个带有必填字段但未提供的架构):

来自 games.service.ts:

  async create(createGameDto: CreateGameDto): Promise<IGame> {
    const createdGame = new this.gameModel(createGameDto);
    return await createdGame.save();
  }

save() 函数返回一个 Promise。

现在我在 game.controller.ts 中有这个

  @Post()
  async create(@Body() createGameDto: CreateGameDto) {
    this.gamesService.create(createGameDto);
  }

处理错误然后返回具有不同 http 状态以及可能是 json 文本的响应的最佳方法是什么? 你通常会抛出一个HttpException,但从哪里来?如果我在 promise 中使用 .catch() 处理错误,我将无法做到这一点。

(刚开始使用nestjs框架)

【问题讨论】:

    标签: typescript nestjs


    【解决方案1】:

    我在我的应用程序中所做的是使用Exception Filters (https://docs.nestjs.com/exception-filters) 和try/catch

      async create(createGameDto: CreateGameDto): Promise<IGame> {
        try {
          const createdGame = new this.gameModel(createGameDto);
          return await createdGame.save();
        } catch (e) {
           // the e here would be MongoError
           throw new InternalServerException(e.message);
        }
      }
    

    【讨论】:

    【解决方案2】:

    首先,您忘记在控制器内的 create 方法中添加 return。这是一个常见的、非常容易误导的错误,我犯了 1000 次并且花了我几个小时来调试。

    捕获异常:

    您可以尝试使用 @Catch 捕获 MongoError。

    对于我的项目,我正在执行以下操作:

    import { ArgumentsHost, Catch, ConflictException, ExceptionFilter } from '@nestjs/common';
    import { MongoError } from 'mongodb';
    
    @Catch(MongoError)
    export class MongoExceptionFilter implements ExceptionFilter {
      catch(exception: MongoError, host: ArgumentsHost) {
        switch (exception.code) {
          case 11000:
            // duplicate exception
            // do whatever you want here, for instance send error to client
        }
      }
    }
    

    然后您可以在控制器中像这样使用它(甚至可以将其用作全局/类范围过滤器):

    import { MongoExceptionFilter } from '<path>/mongo-exception.filter';
    
    @Get()
    @UseFilters(MongoExceptionFilter)
    async findAll(): Promise<User[]> {
      return this.userService.findAll();
    }
    

    (重复异常在 findAll() 调用中没有意义,但你明白了)。

    此外,我强烈建议使用类验证器,如下所述:https://docs.nestjs.com/pipes

    【讨论】:

    • 对我来说这个解决方案不起作用。我有完全相同的设置,除了控制器异步方法是对在 mongodb 上执行 .save() 的服务的调用。自定义 MongoExceptionFilter 不会触发。我猜MongoError 不会被“捕获”,因为当我将 Catch 留空时,它会触发... "@nestjs/core": "^5.4.0", "@nestjs/mongoose": "^5.2 .2", "猫鼬": "^5.4.7",
    • @MarnixHarderwijk 我也遇到了同样的情况。否决这个答案,因为它不起作用。
    【解决方案3】:

    使用 try/catch

    async getUser(id: string, validateUser ?: boolean): Promise<Users> {
        try {
          const user = await this.userModel.findById(id).exec();
          if(!user && validateUser) {
            throw new UnauthorizedException();
          }else if(!user) {
            throw new HttpException(`Not found this id: ${id}`, HttpStatus.NOT_FOUND)
          }
          return user;
        } catch (err) {
          throw new HttpException(`Callback getUser ${err.message}`, HttpStatus.BAD_REQUEST);
        }
    

    【讨论】:

    • 这不是最好的方法,因为你必须在你的应用程序中编写 try/catch 块,在使用 Nestjs 时不建议这样做,因为它有自己的异常系统。
    【解决方案4】:

    你可以在mongoose中使用Error并添加到AllExceptionFilter

    请参阅 NestJS 文档以获取 exception-filters

    import {
      ExceptionFilter,
      Catch,
      ArgumentsHost,
      HttpException,
      HttpStatus,
      InternalServerErrorException
    } from "@nestjs/common";
    
    @Catch()
    export class AllExceptionsFilter implements ExceptionFilter {
      catch(exception: InternalServerErrorException, host: ArgumentsHost) {
        const ctx = host.switchToHttp();
        const response = ctx.getResponse();
        const request = ctx.getRequest();
    
        const status =
          exception instanceof HttpException
            ? exception.getStatus()
            : HttpStatus.INTERNAL_SERVER_ERROR;
    
        /**
         * @description Exception json response
         * @param message
         */
        const responseMessage = (type, message) => {
          response.status(status).json({
            statusCode: status,
            path: request.url,
            errorType: type,
            errorMessage: message
          });
        };
    
        // Throw an exceptions for either
        // MongoError, ValidationError, TypeError, CastError and Error
        if (exception.message.error) {
          responseMessage("Error", exception.message.error);
        } else {
          responseMessage(exception.name, exception.message);
        }
      }
    }
    

    您可以像这样将其添加到 main.ts 中,但这实际上取决于您的用例。您可以在 Nest.js documentation 中查看。

    async function bootstrap() {
    
      const app = await NestFactory.create(AppModule);
    
      app.useGlobalFilters(new AllExceptionsFilter());
    
      await app.listen(3000);
    }
    bootstrap();
    

    希望对你有帮助。

    【讨论】:

      【解决方案5】:

      我做了一些研究,发现这个工作正常。如下创建一个 Mongo 异常过滤器

      import { ExceptionFilter, Catch, ArgumentsHost, HttpStatus } from "@nestjs/common";
      import { MongoError } from 'mongodb';
      import { Response } from 'express';
      
      @Catch(MongoError)
      export class MongoExceptionFilter implements ExceptionFilter {
      
          catch(exception: MongoError, host: ArgumentsHost) {
              switch (exception.code) {
                  case 11000:
                      const ctx = host.switchToHttp();
                      const response = ctx.getResponse<Response>();
                      response.statusCode = HttpStatus.FORBIDDEN;
                      response
                          .json({
                              statusCode: HttpStatus.FORBIDDEN,
                              timestamp: new Date().toISOString(),
                              message: 'You are already registered'
                          });
              }
          }
      }
      
      

      并且不要忘记定义控制器方法如下:

      @UseFilters(MongoExceptionFilter)
        @Post('signup')
        @HttpCode(HttpStatus.OK)
        async createUser(@Body() createUserDto: CreateUserDto) {
          await this.userService.create(createUserDto);
        }
      
      

      希望这对某人有所帮助。干杯!

      【讨论】:

        【解决方案6】:

        今天搞定

        validation-error.filter.ts:

        import { ArgumentsHost, Catch, RpcExceptionFilter } from '@nestjs/common';
        import { Error } from 'mongoose';
        import ValidationError = Error.ValidationError;
        
        @Catch(ValidationError)
        export class ValidationErrorFilter implements RpcExceptionFilter {
        
          catch(exception: ValidationError, host: ArgumentsHost): any {
        
            const ctx = host.switchToHttp(),
              response = ctx.getResponse();
        
            return response.status(400).json({
              statusCode: 400,
              createdBy: 'ValidationErrorFilter',
              errors: exception.errors,
            });
          }
        }
        

        ma​​in.ts:

        import { NestFactory } from '@nestjs/core';
        import { AppModule } from './app.module';
        import { ValidationErrorFilter } from './validation-error.filter';
        
        async function bootstrap() {
          const app = await NestFactory.create(AppModule);
          app.useGlobalFilters(new ValidationErrorFilter());
          await app.listen(process.env.PORT || 3000);
        }
        bootstrap();
        

        结果:

        {
          "statusCode": 400,
          "createdBy": "ValidationErrorFilter",
          "errors": {
            "dob": {
              "properties": {
                "message": "Path `dob` is required.",
                "type": "required",
                "path": "dob"
              },
              "kind": "required",
              "path": "dob"
            },
            "password": {
              "properties": {
                "message": "Path `password` is required.",
                "type": "required",
                "path": "password"
              },
              "kind": "required",
              "path": "password"
            }
          }
        }
        

        【讨论】:

        • 这行得通,谢谢! import { Error } from 'mongoose'; 是我所缺少的。
        【解决方案7】:

        我在这里找到了解决方案,我使用的是两者的结合来捕获不同的错误

        import { ArgumentsHost, Catch, ExceptionFilter, RpcExceptionFilter } from '@nestjs/common';
        import { Error } from 'mongoose';
        import { IDTOError } from '../errors/bad-request-exception.error';
        import ValidationError = Error.ValidationError;
        import { MongoError } from 'mongodb';
        
        
        @Catch(MongoError)
        export class MongoExceptionFilter implements ExceptionFilter {
          catch(exception: MongoError, host: ArgumentsHost) {
            // switch (exception.code) {
            //   case 11000:
            //   default: console.log(exception,'ALERT ERROR CATCHED');
            //     // duplicate exception
            //     // do whatever you want here, for instance send error to client
        
        
            //     /** MAIGOD */
            // }
            const ctx = host.switchToHttp(),
              response = ctx.getResponse();
        
            return response.status(400).json(<IDTOError>{
              statusCode: 400,
              createdBy: 'ValidationErrorFilter, Schema or Model definition',
              errors: exception,
            });
        
          }
        }
        
        @Catch(ValidationError)
        export class ValidationErrorFilter implements RpcExceptionFilter {
        
          catch(exception: ValidationError, host: ArgumentsHost): any {
        
            const ctx = host.switchToHttp(),
              response = ctx.getResponse();
        
            return response.status(400).json(<IDTOError>{
              statusCode: 400,
              createdBy: 'ValidationErrorFilter, Schema or Model definition',
              errors: exception.errors,
            });
          }
        }
        

        【讨论】:

          【解决方案8】:

          我正在使用 Moongose,但此处或其他问题中的解决方案均不适合我;我按照文档示例做了这个,它确实对我有用。

          src\filters\mongo-exception.filter.ts

          import { ArgumentsHost, Catch, ExceptionFilter, HttpStatus } from '@nestjs/common';
          
          import * as MongooseError from 'mongoose/lib/error'; // I couldn't see the error class is being exported from Mongoose
          
          @Catch(MongooseError)
          export class MongoExceptionFilter implements ExceptionFilter {
            catch(exception: MongooseError, host: ArgumentsHost) {
              const ctx = host.switchToHttp();
              const response = ctx.getResponse();
              // const request = ctx.getRequest();
          
              let error;
          
              switch (exception.name) {
                case 'DocumentNotFoundError': {
                  error = {
                    statusCode: HttpStatus.NOT_FOUND,
                    message: "Not Found"
                  }
                  break;
                }
                // case 'MongooseError': { break; } // general Mongoose error
                // case 'CastError': { break; }
                // case 'DisconnectedError': { break; }
                // case 'DivergentArrayError': { break; }
                // case 'MissingSchemaError': { break; }
                // case 'ValidatorError': { break; }
                // case 'ValidationError': { break; }
                // case 'ObjectExpectedError': { break; }
                // case 'ObjectParameterError': { break; }
                // case 'OverwriteModelError': { break; }
                // case 'ParallelSaveError': { break; }
                // case 'StrictModeError': { break; }
                // case 'VersionError': { break; }
                default: {
                  error = {
                    statusCode: HttpStatus.INTERNAL_SERVER_ERROR,
                    message: "Internal Error"
                  }
                  break;
                }
              }
          
              response.status(error.statusCode).json(error);
            }
          }
          

          src\main.ts

          import { MongoExceptionFilter } from './filters/mongo-exception.filter';
          
          async function bootstrap() {
            // .......
          
            app.useGlobalFilters(new MongoExceptionFilter); // Use Mongo exception filter
          
            await app.listen(3000);
          }
          bootstrap();
          
          

          【讨论】:

            【解决方案9】:

            我正在使用带有记录器的全局异常过滤器:(WINSTON),它可以捕获几乎所有错误,包括 MongoDB 错误,不知道它是否符合标准但对我有用,我关注了Nest Js Doc 一些我的自定义需求!

            import {
              ArgumentsHost,
              Catch,
              ExceptionFilter,
              HttpException,
              HttpStatus,
              Inject,
              LoggerService,
            } from '@nestjs/common';
            import { Response } from 'express';
            import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston';
            
            @Catch()
            export class HttpExceptionFilter implements ExceptionFilter {
              constructor(
                @Inject(WINSTON_MODULE_NEST_PROVIDER)
                private readonly logger: LoggerService,
              ) {}
              catch(exp: any, host: ArgumentsHost) {
                console.log(exp);
                this.logger.error(exp);
                const context = host.switchToHttp();
                const response = context.getResponse<Response>();
                const request = context.getRequest<Request>();
            
                const hasKey = Object.keys(exp).length > 0 && exp.hasOwnProperty('response') ? true : false;
                const isHttpInstance = exp instanceof HttpException ? true : false;
            
                const validErrors = hasKey && Array.isArray(exp.response.message) ? exp.response.message : [];
                const statusCode = isHttpInstance ? exp.getStatus() : HttpStatus.INTERNAL_SERVER_ERROR;
                const type = hasKey && exp.response.type ? exp.response.type : 'some_thing_went_error';
                const message = isHttpInstance ? exp.message : 'Oops Something went wrong!';
                const error = hasKey ? exp.response.error : exp;
            
                response.status(statusCode).json({
                  message,
                  type,
                  validationErrors: validErrors,
                  statusCode,
                  error,
                  timestamp: new Date().toISOString(),
                  path: request.url,
                });
              }
            }
            

            这将是 MongoDB 错误的响应:

                {
                "message": "Oops Something went wrong!",
                "type": "some_thing_went_error",
                "validationErrors": [],
                "statusCode": 500,
                "error": {
                    "errors": {
                        "subRegion": {
                            "name": "ValidatorError",
                            "message": "Path `subRegion` is required.",
                            "properties": {
                                "message": "Path `subRegion` is required.",
                                "type": "required",
                                "path": "subRegion"
                            },
                            "kind": "required",
                            "path": "subRegion"
                        }
                    },
                    "_message": "ServiceRequest validation failed",
                    "name": "ValidationError",
                    "message": "ServiceRequest validation failed: subRegion: Path `subRegion` is required."
                },
                "timestamp": "2022-02-08T07:43:35.962Z",
                "path": "/v1/service-request"
            }
            

            这将是正常错误时的响应:

            {
                "message": "Bad Request Exception",
                "type": "some_thing_went_error",
                "validationErrors": [
                    "isEmergency must be one of the following values: true, false",
                    "isEmergency must be a string",
                    "isEmergency should not be empty"
                ],
                "statusCode": 400,
                "error": "Bad Request",
                "timestamp": "2022-02-08T07:47:25.183Z",
                "path": "/v1/service-request"
            }
            

            如果您希望 type 是自定义类型,可以从应用程序中抛出,您可以使用以下代码

                throw new HttpException(
                  {
                    status: HttpStatus.FORBIDDEN,
                    type: 'otp_verification_error',
                    message: 'Please verify your mobile number to complete the signUp process!',
                  },
                  HttpStatus.FORBIDDEN,
                );
            

            【讨论】:

              猜你喜欢
              • 2012-07-30
              • 2021-09-18
              • 1970-01-01
              • 2020-07-18
              • 1970-01-01
              • 2018-08-29
              • 2019-05-21
              • 2019-08-04
              • 2022-06-11
              相关资源
              最近更新 更多