Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NestJS - ValidationPipe in WebsocketGateway returns Internal Server Error

I'm trying to add some validation to my WebSocketGateway in NestJS. Here's the code:

// MessageDTO

import { IsNotEmpty, MinLength } from 'class-validator';

export class MessageDTO {
  @IsNotEmpty()
  username: string;

  @IsNotEmpty()
  @MinLength(10)
  text: string;
}
// Gateway

import { ValidationPipe, UsePipes } from '@nestjs/common';
import { MessageBody, SubscribeMessage, WebSocketGateway, WsResponse } from '@nestjs/websockets';
import { MessageService } from './message/message.service';
import { MessageDTO } from './message/message.dto';
import { Message } from './message/message.entity';

@WebSocketGateway()
export class AppGateway {
  constructor(private readonly messageService: MessageService) {}

  @UsePipes(new ValidationPipe())
  @SubscribeMessage('message')
  async handleMessage(@MessageBody() dto: MessageDTO): Promise<WsResponse<Message>> {
    const message = await this.messageService.saveMessage(dto);
    return { event: 'message', data: message };
  }
}

Now, when I try to send a message that doesn't meet validation rules, it errors out, but the client always receives { status: 'error', message: 'Internal server error'}. Also, Nest logs the error to the console (which I belive should not happen...?):

thing_api | Error: Bad Request Exception
thing_api |     at ValidationPipe.exceptionFactory (/usr/src/app/node_modules/@nestjs/common/pipes/validation.pipe.js:78:20)
thing_api |     at ValidationPipe.transform (/usr/src/app/node_modules/@nestjs/common/pipes/validation.pipe.js:50:24)
thing_api |     at processTicksAndRejections (internal/process/task_queues.js:89:5)
thing_api |     at async resolveParamValue (/usr/src/app/node_modules/@nestjs/websockets/context/ws-context-creator.js:104:31)
thing_api |     at async Promise.all (index 0)
thing_api |     at async pipesFn (/usr/src/app/node_modules/@nestjs/websockets/context/ws-context-creator.js:106:13)
thing_api |     at async /usr/src/app/node_modules/@nestjs/websockets/context/ws-context-creator.js:41:17
thing_api |     at async AppGateway.<anonymous> (/usr/src/app/node_modules/@nestjs/websockets/context/ws-proxy.js:11:32)
thing_api |     at async WebSocketsController.pickResult (/usr/src/app/node_modules/@nestjs/websockets/web-sockets-controller.js:85:24)

However, if I use the same DTO and validation pipe in regular controller, it works like a charm - with malformed payload I get properly formatted error message. Can somebody point out what I'm doing wrong?

like image 315
someguy Avatar asked Mar 19 '20 00:03

someguy


Video Answer


2 Answers

You can rewrite the default websocket filter, to catch both http exception and websocket exception.

import { ArgumentsHost, Catch, HttpException } from '@nestjs/common';
import { WsException } from '@nestjs/websockets';
import { Socket } from 'socket.io';

@Catch(WsException, HttpException)
export class WsExceptionFilter {
  public catch(exception: HttpException, host: ArgumentsHost) {
    const client = host.switchToWs().getClient();
    this.handleError(client, exception);
  }

  public handleError(client: Socket, exception: HttpException | WsException) {
    if (exception instanceof HttpException) {
      // handle http exception
    } else {
      // handle websocket exception
    }
  }
}

Then use it in your gateway

@UseFilters(WsExceptionFilter)
@WebSocketGateway()
export class WorkspacesGateway {}
like image 188
blastz Avatar answered Oct 18 '22 19:10

blastz


BadRequestException is a child class of HttpException. Nest's default exception handler for websockets checks to see if the caught exception is an instanceof WsException and if not returns an unknown exception.

To get around this, you can implement a filter that catches BadRequestException and transforms it to an appropriate WsException before having Nest's exception filter handle the exception from there.

@Catch(BadRequestException)
export class BadRequestTransformationFilter extends BaseWsExceptionFilter {
  catch(exception: BadRequestException, host: ArgumentHost) {
    const properException = new WsException(exception.getResponse());
    super.catch(properException, host);
  }
}
like image 24
Jay McDoniel Avatar answered Oct 18 '22 18:10

Jay McDoniel