Search code examples
node.jstypescriptnestjshttpexception

NestJS Exception filter messes up error array if it comes from ValidationPipe


So I use the ValidationPipe to validate my DTOs in NestJS, like this:

// auth.dto.ts
export class AuthDto {
  @IsEmail()
  @IsNotEmpty()
  email: string;
}

Without the Exception filter the error message works as intended. I leave the email field empty and I receive an array of error messages:

// Response - Message array, but no wrapper
{
  "statusCode": 400,
  "message": [
    "email should not be empty",
    "email must be an email"
  ],
  "error": "Bad Request"
}

Perfect. Now I want to implement a wrapper for the error messages, so I create a new filter and add it to to bootstrap:

// main.ts
async function bootstrap() {
  // ...
  app.useGlobalFilters(new GlobalExceptionFilter());
}
bootstrap();
// global-exception.filter.ts
import {
  ArgumentsHost,
  Catch,
  ExceptionFilter,
  HttpException,
  HttpStatus,
} from '@nestjs/common';
import { Response } from 'express';
import { IncomingMessage } from 'http';

export const getStatusCode = <T>(exception: T): number => {
  return exception instanceof HttpException
    ? exception.getStatus()
    : HttpStatus.INTERNAL_SERVER_ERROR;
};

export const getErrorMessage = <T>(exception: T): string => {
  return exception instanceof HttpException
    ? exception.message
    : String(exception);
};

@Catch()
export class GlobalExceptionFilter<T> implements ExceptionFilter {
  catch(exception: T, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    const request = ctx.getRequest<IncomingMessage>();
    const statusCode = getStatusCode<T>(exception);
    const message = getErrorMessage<T>(exception);

    response.status(statusCode).json({
      error: {
        timestamp: new Date().toISOString(),
        path: request.url,
        statusCode,
        message,
      },
    });
  }
}

It works great for most of my errors:

// Response - Good format (wrapped), single message expected
{
  "error": {
    "timestamp": "2022-05-11T19:54:59.093Z",
    "path": "/auth/signup",
    "statusCode": 400,
    "message": "Email already in use"
  }
}

But when I get a ValidationError from the ValidationPipe it should give me an array or messages like before, but it gives this message instead:

// Response - Wrapper: check, single message instead of array
{
  "error": {
    "timestamp": "2022-05-11T19:59:17.282Z",
    "path": "/auth/signup",
    "statusCode": 400,
    "message": "Bad Request Exception" // it should be "message": ["not empty", "must be email"]
  }
}

The exception object in my exception filter has a response field which contains the message array:

// HttpException object inside the filter class
{
  response: {
    statusCode: 400,
    message: [ 'email should not be empty', 'email must be an email' ],
    error: 'Bad Request'
  },
  status: 400
}

But exception.response.message doesn't work, because the field is private and TypeScript throws an error:
Property 'response' is private and only accessible within class 'HttpException'.

Does any of you know how could I reach the message array, so I could format my error response properly?

EDIT: Sorry for the long post!


Solution

  • As @tobias-s commented, there's a workaround, which solved the problem:

    Try exception["response"]["message"]. This bypasses the private restriction