How to catch validation error message - Nestjs Gateway Socket.io - node.js

I have a validation pipe that checks if a user-sent JSON data is valid. Validator is working great, but I can not catch the error and then send it to the client. I know about exeptionFactory property in ValidationPipe constructor, but I still can not catch an error and it still logs in the console.
[Nest] 11820 - 01/07/2023, 11:12:25 PM ERROR [WsExceptionsHandler] Bad Request Exception
BadRequestException: Bad Request Exception
Here is a code
#SubscribeMessage(Stream.Transactions)
#UseGuards(JwtAuthGuard)
#UsePipes(new ValidationPipe())
handleTransactions(
clien: any,
#MessageBody() data: TransactionObject,
) {
let req = this.streamService.transaction(data)
return { event: Stream.Transactions, data: req }
}

I think you can create a filter to get the error and return some specific data. But you can do it in a couple ways: Creating a Websocket exception filter to catch the error or use the exceptionFactory you mention in your question to generate a WsException and catch into the filter.
The main problem (if I'm not wrong) is the pipe does not return a WsException but a BadRequestException.
So to use an exception filter, how the exception is Bad Request, you can use this one:
#Catch(BadRequestException)
export class BadRequestExceptionsFilter extends BaseWsExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
// Here you have the exception and you can check the data
const wsException = new WsException(exception.getResponse())
super.catch(wsException, host);
}
}
Note how this code follows the documentation
And now not only you can get and read the exception but also you can create a properly WsException.
To use this you can add #UseFilters(BadRequestExceptionsFilter) into your gateway.
Another way is to catch WS and HTTP exceptions and handle properly you want, something similar to this example. The idea can be to catch the HTTP exception only to get Bad Request so your desired context will be always WS:
#Catch(WsException, HttpException)
export class WsAndHttpExceptionFilter {
public catch(exception: HttpException, host: ArgumentsHost) {
// Here you have the exception and you can check the data
const ctx = host.switchToWs()
const client = ctx.getClient() as WebSocket;
client.send(JSON.stringify({ /* ... */ }))
}
}
Or also you can try to create the exceptionFactory to return the WsException.
Into the decorator:
#UsePipes(new ValidationPipe({
exceptionFactory(validationErrors: ValidationError[] = []) {
// Here are the errors
if (this.isDetailedOutputDisabled) {
return new WsException();
}
const errors = this.flattenValidationErrors(validationErrors);
return new WsException(errors);
}
}))
Check how factories are done into the project and how this code tries to follow the same way but returning WsException.
Or in a class extending ValidationPipe:
#Injectable()
export class WSValidationPipe extends ValidationPipe {
createExceptionFactory() {
return (validationErrors: ValidationError[] = []) {
// Here are the errors
if (this.isDetailedOutputDisabled) {
return new WsException();
}
const errors = this.flattenValidationErrors(validationErrors);
return new WsException(errors);
}
}
}
By the way, you can also #Catch(WsException) (and only this exception which is clearer) once are thrown if it is util for you to return the data you want:
#Catch(WsException)
export class WebsocketExceptionsFilter extends BaseWsExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
const ctx = host.switchToWs()
const client = ctx.getClient() as WebSocket;
const data = ctx.getData();
client.send(JSON.stringify({
event: 'error',
ok: false,
error: exception.getError(),
data: data // Or whatever you want to add
}))
}
}
I can't test now all this code but I've used some of these snippets and works for me, hope it helps.

Related

How to return json message from callback instead of triggering error | NestJs File upload

I am working on Nestjs Multer File Upload, I have created a file filter for FileInterceptor, I wanna send a response back instead of sending an error, I have to send a JSON with the message as "file type is not supported".
export const FileFilter = (req, file, callback) => {
if (!file.originalname.match(/\.(jpg|jpeg|png)$/)) {
return callback(new Error('Only image files supported!'), false);
}
callback(null, true);
}
Instead of sending new Error(), I would like to send
res.send({status:"error",message:"File types does not supported"});
This action is performed by a built-in global exception filter, which handles exceptions of type HttpException (and subclasses of it). When an exception is unrecognised (is neither HttpException nor a class that inherits from HttpException), the built-in exception filter generates the following default JSON response:
{
"statusCode": 500,
"message": "Internal server error"
}
In many cases, you will not need to write custom exceptions, and can use the built-in Nest HTTP exception, as described in the next section. If you do need to create customised exceptions, it's good practice to create your own exceptions hierarchy, where your custom exceptions inherit from the base HttpException class. With this approach, Nest will recognise your exceptions, and automatically take care of the error responses. Let's implement such a custom exception:
import { ExceptionFilter, Catch, HttpException, ArgumentsHost, HttpStatus, BadRequestException } from '#nestjs/common';
#Catch()
export class ErrorFilter implements ExceptionFilter {
catch(error: Error, host: ArgumentsHost) {
let response = host.switchToHttp().getResponse();
let status = (error instanceof HttpException) ? error.message: HttpStatus.INTERNAL_SERVER_ERROR;
if (status.statusCode === HttpStatus.BAD_REQUEST) {
return response.status(HttpStatus.BAD_REQUEST).send(status)
}
if (status.statusCode === HttpStatus.NOT_FOUND) {
return response.status(HttpStatus.NOT_FOUND).send(status)
}
if (status.statusCode === HttpStatus.UNAUTHORIZED)
return response.status(status.statusCode).send(status)
if (status.statusCode === HttpStatus.NOT_FOUND)
return response.status(status).send(status)
if (status === HttpStatus.INTERNAL_SERVER_ERROR) {
if (process.env.NODE_ENV === 'production') {
console.error(error.stack);
return response.status(status).render('views/500');
}
else {
let message = error.stack;
return response.status(status).send(message);
}
}
}
}
For more details, you can check the Nest.js documentation custom exception filter.

AWS Lambda with typescript getting Cannot read property of undefined inside async handler

Typescript newbie here. I am working on an AWS Lambda function by using typescript with classes. I am exporting an async handler at the end. When I invoke my function from AWS SAM CLI then I am getting error of;
{"errorType":"TypeError","errorMessage":"Cannot read property 'test' of undefined","stack":["TypeError: Cannot read property 'test' of undefined"," at Runtime.handler (/var/task/src/lambda/create-cost-lambda.js:12:56)"," at Runtime.handleOnce (/var/runtime/Runtime.js:66:25)"]}
create-cost-lambda.ts
class CreateCostLambda {
private readonly foobarRepository: FoobarRepository;
constructor() {
this.foobarRepository = new FoobarRepository();
}
async handler(event: APIGatewayProxyEventV2) : Promise<APIGatewayProxyResultV2> {
const result = await this.foobarRepository.test();
console.log(result);
return {
body: JSON.stringify(result),
statusCode: 200,
};
}
}
export const { handler } = new CreateCostLambda();
Here is a very basic class represents a repository.
foobar-repository.ts
export class FoobarRepository {
private readonly awesomeValue: string;
constructor() {
this.awesomeValue = 'John Doe';
}
async test(): Promise<string> {
return this.awesomeValue;
}
}
I am almost sure it is because of the way I am exporting the handler and how aws-sam internally runs the handler. But I might be wrong and it can be typescript thing that I am missing. Please let me know if you need more information and thanks a lot for the help!
The short version is if you pass a function from an class, it loses it's reference to this.
I would solve this as follows:
const createCostLambda = new CreateCostLambda();
export const handler = createCostLambda.handler.bind(createCostLambda);
You can also ask yourself, does this need to be a class? The answer is: probably not. There's nothing gained from this in your sample.
const foobarRepository = new FoobarRepository();
export async function handler(event: APIGatewayProxyEventV2) : Promise<APIGatewayProxyResultV2> {
const result = await foobarRepository.test();
console.log(result);
return {
body: JSON.stringify(result),
statusCode: 200,
};
}
Fewer lines, no unneeded state. Javascript is not Java =)

NestJS - async operation inside error filter

In our NestJS-app we've set up a custom error filter, that catches a certain type of error. For those errors we need to perform a request to elasticsearch in order to log the corresponding error information. Since the elasticsearch request is async I've defined the catch method async:
#Catch(MyExceptionType)
#Injectable()
export class MyExceptionFilter implements ExceptionFilter {
constructor(private readonly elasticsearchService: ElasticsearchService) { }
async catch(exception: MyExceptionType, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const request = ctx.getRequest<MyRequestModel>();
const response = ctx.getResponse<MyResponseModel>();
const elasticSearchPayload = PayloadBuilder.of(request, exception);
await this.elasticsearchService.report(elasticSearchPayload);
// ...
response.status(exception.getStatus()).json({...});
}
}
Now - so far this works fine, but I'm wondering if this is actually ok to do, as the ExceptionFilter interface strictly declares catch to be a synchronous method.
Could we run into trouble doing this?
ExceptionFilters are to define your error handling logic. I don't think it should be an issue having it async, Nest just won't wait for the logic to finish, however, it shouldn't invoke any other exception handlers due to how it's custom filter code is written.

NestJS handle service exceptions

I'm working on a NestJS app where my services are not always called by a controller or any http request at all. Rather some services are called by a cron schedule to periodically fetch data.
What would be the best way to handle errors in this scenario? I implemented a "catch-all" exception filter, but when my service is called "internally" (not by a controller/request), there error does not get caught and I have an uncaught promise error.
See my question here: Use global nest module in decorator
This decorator catches errors of a class method and logs them. The logging part is not necessary, you could implement your own error handling logic.
import { Inject } from '#nestjs/common';
import { LoggerService } from '../../logger/logger.service';
export function logErrorDecorator(bubble = false) {
const injectLogger = Inject(LoggerService);
return (target: any, propertyKey: string, propertyDescriptor: PropertyDescriptor) => {
injectLogger(target, 'logger'); // this is the same as using constructor(private readonly logger: LoggerService) in a class
//get original method
const originalMethod = propertyDescriptor.value;
//redefine descriptor value within own function block
propertyDescriptor.value = async function(...args: any[]) {
try {
return await originalMethod.apply(this, args);
} catch (error) {
const logger: LoggerService = this.logger;
logger.setContext(target.constructor.name);
logger.error(error.message, error.stack);
// rethrow error, so it can bubble up
if (bubble) {
throw error;
}
}
};
};
}
With this decorator you can simply add the logErrorDecorator() to your service class methods

Scope in NodeJS / Firebase promise

I'm trying to develop a NodeJS app connecting to Firebase. I can connect successfully, but I'm unable to figure how to manage the scope in the then call.
I'm using NodeJS 6.9.2
My test implementation looks like this:
const EventEmitter = require('events');
const fb = require('firebase')
class FireGateway extends EventEmitter {
constructor() {
super();
if ( this.instance ) {
return this.instance;
}
// INIT
var fbConfig = {
apiKey: "xxxxx",
authDomain: "xxxxx.firebaseapp.com",
databaseURL: "https://xxxxx.firebaseio.com/"
};
fb.initializeApp(fbConfig)
this.instance = this;
this.testvar = "aaa";
}
login() {
fb.auth().signInWithEmailAndPassword ("email", "pwd")
.catch(function(error) {
// Handle Errors here.
}).then( function(onresolve, onreject) {
if (onresolve) {
console.log(this.testvar);
// "Cannot read property 'testvar' of undefined"
this.emit('loggedin');
// error as well
}
})
}
}
module.exports = FireGateway;
------
...
var FireGateway = require('./app/fireGateway');
this.fireGW = new FireGateway();
this.fireGW.login();
....
Any idea how can I manage it?
The callback passed to then is being called asynchronously from another context, so the this doesn't correspond to the instantiated object.
Using ES6 arrow functions you can keep your object context, since an arrow function does not create its own this context.
By the way, the syntax you are using in the then method is not correct, then accepts two callbacks with one argument each one. Check the syntax here.
The catch before the then is not necessary as well I think, it would make more sense to put it at the end.
It would be something like this:
login() {
fb.auth().signInWithEmailAndPassword("email", "pwd")
.then(
(onResolve) => {
console.log(this.testvar);
this.emit('loggedin');
},
(onReject) = > {
// error handling goes here
});
}
On the other hand, it seems login method is doing an asynchronous operation, so you might want to wait for it to finish in your code. I would make the login method return a Promise, so you can wait for it outside:
login() {
return fb.auth().signInWithEmailAndPassword("email", "pwd")
...
}

Resources