NestJS handle service exceptions - nestjs

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

Related

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

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.

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.

Nestjs: Retrieve the request / context from a Decorator

I am working on a NestJS project,
I'm trying to get the executionContext accessible in a logger to filter the logs by request.
I have one logger instance per injectable, and I would like to keep this behavior (So the scope of the injectable is default).
To do this, I'm trying to create a decorator that gets the context from the request and passes it to the child services (as in the logger), to finally get the context in the logger...
I'm not sure to be clear... For now, here is my code:
export const Loggable = () => (constructor: Function) => {
for (const propertyName of Reflect.ownKeys(constructor.prototype)) {
let descriptor = Reflect.getOwnPropertyDescriptor(constructor.prototype, propertyName);
const isMethod = descriptor.value instanceof Function;
if (!isMethod)
continue;
const originalMethod = descriptor.value;
const routeArgsMetada = Reflect.getMetadata(ROUTE_ARGS_METADATA, constructor, propertyName as string);
descriptor.value = function (...args: any[]) {
const result = originalMethod.apply(this, args);
//TODO : retrieve the request / contextExecution
//TODO : pass the request / contextExecution to children functions...
return result;
};
Reflect.defineProperty(constructor.prototype, propertyName, descriptor);
Reflect.defineMetadata(ROUTE_ARGS_METADATA, routeArgsMetada, constructor, propertyName as string);
}
};
This #Loggable() decorator would be attached to all injectable classes that need to log or throw execution context
Is that possible ? If not why ?
PS: I'm wondering, how could the #Guard annotation get the context? and how could the #Req annotations get the request?
https://github.com/nestjs/nest/tree/master/packages/common/decorators/http
https://github.com/nestjs/nest/blob/master/packages/common/decorators/core/use-guards.decorator.ts
How #Req does get the Request?
Download source of NestJS from here: https://github.com/nestjs/nest
and look for 'RouteParamtypes.REQUEST' in TS files. You will find them here:
route-params.decorator.ts
route-params-factory.ts
As you can see decorators generally don't do too much. They just add some metadata to classes, methods, and arguments. All the rest do the framework.
Here #Req only creates a special parameter decorator during startup which is processed by RouteParamsFactory before calling a method.
export const Request: () => ParameterDecorator = createRouteParamDecorator(
RouteParamtypes.REQUEST,
);
So Request is not retrieved by the #Req decorator itself. It only asks the NestJS framework to fill the annotated method parameter with reference of Request before calling the method.
BTW I also struggling with the same problem as you. I also was looking for a solution on how to access ExecutionContext from decorators. But decorators can access only annotated targets (classes, handlers, arguments, ...)
I think ExecutionContext only can be accessed directly from:
pipes
guards
interceptors
or from argument decorators this way:
https://docs.nestjs.com/custom-decorators#param-decorators
import { createParamDecorator, ExecutionContext } from '#nestjs/common';
export const User = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.user;
},
);
NOTE: You can find source of createParamDecorator() in create-route-param-metadata.decorator.ts .

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.

Interceptor not catching error thrown by guard in nestjs

I have a global guard that is registered in the application in common.module.ts which is a global module.
const HeaderGuardGlobal = {
provide: APP_GUARD,
useClass: HeaderGuard
};
#Global()
#Module({
imports: [ LoggerModule ],
providers: [ HeaderGuardGlobal ],
exports: []
})
header.guard.ts:
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const userName = request.headers[HEADERS.USER_NAME];
if(!userName) {
throw new HttpException('Forbidden', HttpStatus.FORBIDDEN);
}
I have a control-scoped interceptor authenticate-header.interceptor.ts
#Injectable()
export class setAuthenticateHeaderInterceptor<T> implements NestInterceptor<T, Response<T>> {
public constructor() {}
intercept(context: ExecutionContext, next: CallHandler): Observable<Response<T>> {
const req = context.switchToHttp().getRequest();
const res = context.switchToHttp().getResponse();
return next
.handle()
.pipe(
catchError(err => {
console.log('ERROR: ', err);
res.setHeader('sampleKey', 'sampleValue');
return throwError(err);
})
)
}
}
user.controller.ts:
#Controller('user')
#UseInterceptors(setAuthenticateHeaderInterceptor)
export class ClientController {
What I'm trying to achieve is that when header.guard.ts throws Forbidden exception, the authenticate-header.interceptor.ts would catch the exception and will propagate it to the global http-filter, but before doing that I want to add a header to the response object.
The issue I'm facing is when the exception is being thrown by the guard, the interceptor is unable to catch it. However, when the same error is thrown either from the route handler or from service, the interceptor is able to catch it.
I went through the request-lifecycle to understand the execution context, and found the below statement in interceptors section.
any errors thrown by pipes, controllers, or services can be read in the catchError operator of an interceptor.
The statement doesn't say anything about guards, so I'm assuming what I'm trying to achieve is not possible.
I'm unable to figure out why an error thrown inside a guard is not being caught by the interceptor. The above code snippets only include the parts which I thought were necessary for the question, if anybody feels like more info is needed. then I'll provide it.
The docs correctly state, as you mentioned,
any errors thrown by pipes, controllers, or services can be read in the catchError operator of an interceptor.
Guards, as noted below in the summary section are executed before interceptors are, and as such, their errors are not able to be caught in the interceptor's catchError method. Your best bet would be to make a filter that extends your GlobalFilter, add in your logic there, then call super.catch(exception) to call the rest of the logic.

Resources