Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to handle mongoose error with nestjs

I followed the example from https://docs.nestjs.com/techniques/mongodb

The issue is when there is a mongoose validation error (e.g i have a schema with a required field and it isn't provided):

From games.service.ts:

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

The save() function returns a Promise.

Now i have this in the game.controller.ts

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

What is the best way to handle an error and then return a response with a different http status and maybe a json text? You would usually throw a HttpException but from where? I can't do that if i handle the errors using .catch() in the promise.

(Just started using the nestjs framework)

like image 685
Edgar Luque Avatar asked Jun 14 '18 18:06

Edgar Luque


Video Answer


3 Answers

Nail it today

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,
    });
  }
}

main.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();

result:

{
  "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"
    }
  }
}
like image 119
BoSkiv Avatar answered Oct 26 '22 02:10

BoSkiv


First, you forgot to add return in your create method inside the controller. This is a common, very misleading mistake I made a thousand of times and took me hours to debug.

To catch the exception:

You could try to catch MongoError using @Catch.

For my projects I'm doing the following:

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
    }
  }
}

You can then just use it like this in your controller (or even use it as a global / class scoped filter):

import { MongoExceptionFilter } from '<path>/mongo-exception.filter';

@Get()
@UseFilters(MongoExceptionFilter)
async findAll(): Promise<User[]> {
  return this.userService.findAll();
}

(Duplicate exception doesn't make sense here in a findAll() call, but you get the idea).

Further, I would strongly advise to use class validators, as described here: https://docs.nestjs.com/pipes

like image 41
1FpGLLjZSZMx6k Avatar answered Oct 26 '22 01:10

1FpGLLjZSZMx6k


You can use Error in mongoose and add it in AllExceptionFilter

Please refer to NestJS documentation for 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);
    }
  }
}

You can add it in the main.ts like so but it really depends on your use case. You can check it in the Nest.js documentation.

async function bootstrap() {

  const app = await NestFactory.create(AppModule);

  app.useGlobalFilters(new AllExceptionsFilter());

  await app.listen(3000);
}
bootstrap();

Hope it helps.

like image 40
Richard Vergis Avatar answered Oct 26 '22 02:10

Richard Vergis