Clarification regarding NestJS Interceptors order of execution - nestjs

I have a controller handler that return a string.
// Controller.ts
import { Controller, Get, UseInterceptors } from '#nestjs/common';
import { UpperInterceptor } from './upper.interceptor';
import { LowerInterceptor } from './lower.interceptor';
#Controller()
export class AppController {
#Get()
#UseInterceptors(LowerInterceptor, UpperInterceptor)
getHello(): string {
return 'Hello'
}
}
I have attached 2 interceptors, Lower and Upper which do change the string case accordingly to their name.
// lower.interceptor.ts
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '#nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators'
#Injectable()
export class LowerInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
console.log('lower')
return next
.handle()
.pipe(
map((e) => {
console.log('calling lowercase')
return e.toLowerCase()
}),
);
}
}
// upper.interceptor.ts
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '#nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators'
#Injectable()
export class UpperInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
console.log('upper')
return next
.handle()
.pipe(
map((e) => {
console.log('calling uppercase')
return e.toUpperCase()
}),
);
}
}
I would expect the return value to be HELLO but it is hello
As far as I can see Lower interceptor is executed first, but the map that it pipes to the observable is executed after the map of the Upper interceptor and the return string is hence hello.
Do you know why the interceptors are executed in order as expected but the callback they map through pipe is executed in inverted order?
Is this related to nest or just rxjs? Sorry but I am new to both.

It's an RxJS thing. This Stackblitz essentially shows what's going on, even if not explicitly written in your server. Each interceptor is getting chained into the next one. The interceptors follow a First In, Last Out stack frame, so you see lower being logged before upper, but then you see the output as lowercase instead of uppercase like you originally expected. I've added in some tap methods as well to illustrate where in the call chain everything happens with Observbales.

The order is defined by the order of the decorator arguments #UseInterceptors(...). It works like a function call stack in recursive calls. After executing the handler (return from the function), the call stack is unwound again:
1. Lower Interceptor
2. Upper Intereptor
3. next.handle()
4. Upper Interceptor (Transform to uppercase)
5. Lower Interceptor (Transform to lowercase)

Related

Nestjs Microservice's Controller method is not calling when Interceptor is attached

I am using an interceptor in both Rest API controller and Microservice controller, it is working fine in Rest API controller(Interceptor runs and then the api controller mthod runs) but when the same interceptor is used on the Microservice controller, it just runs the interceptor but the controller method is not called.
I tried searching the Nestjs issues but didn't find any solution. I already checked this issue
Interceptor
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '#nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
#Injectable()
export class LoggingInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
console.log('Inside the interceptor...');
return next.handle();
}
}
Controller method
#EventPattern('test-event')
#UseInterceptors(LoggingInterceptor)
myMethod() {
console.log('Inside the controller method')
}
Current output:
Inside the interceptor...
Expected output:
Inside the interceptor...
Inside the controller method

How to make NestJS global exception filter catch an exception thrown by an interceptor?

Is it possible to make an interceptor throw an error an make it so it will be caught by the global exceptions filter?
Right now, I'm still very much learning how to use NestJS, and I have a small HTTP application with a Global Exception Filter (just like the one one the docs)... Is it possible to make that filter catch errors thrown by an interceptor? Like... My interceptor catches exceptions and throws back a different exception?
You can setup a new global interceptor and use Rxjs observables to achieve this.
Something like this should do the trick:
export class ErrorInterceptor implements NestInterceptor {
intercept(_context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
catchError((err) => {
// do stuff
}),
);
}
}
You need to define an exception filter, and append the status and message as a json to the response. something like this :
import { ArgumentsHost, Catch, ExceptionFilter } from '#nestjs/common';
#Catch()
export class MyExceptionsFilter implements ExceptionFilter {
constructor() {
console.log('init');
}
catch(exception, host: ArgumentsHost) {
const res = host.switchToHttp();
const response = res.getResponse();
response.status(400).json(exception.response);
}
}
then, you call this exception filter on your app.main :
import { MyExceptionsFilter } from '.';
async function bootstrap() {
const app = await NestFactory.create(SOME-MODULE);
app.useGlobalFilters(new MyExceptionsFilter());
}
bootstrap();

NestJs - Validate request body using class-validator having 2 options for body class

I have a rest call, which might receive body of type classA or classB.
I need to keep it as 2 different classes.
Example -
// classes -
class ClassA {
#IsString()
#Length(1, 128)
public readonly name: string;
#IsString()
#Length(1, 128)
public readonly address: string;
}
class ClassB {
#IsString()
#Length(1, 10)
public readonly id: string;
}
// my request controller -
#Post('/somecall')
public async doSomething(
#Body(new ValidationPipe({transform: true})) bodyDto: (ClassA | ClassB) // < not validating any of them..
): Promise<any> {
// do something
}
The issue is, that when having more than one class, body is not validated.
How can I use 2 or more classes and validate them using class-validator?
I don't want to use same class..
Thank you all :)
I don't want to use same class..
Then it won't be possible, at least not with Nest's built-in ValidationPipe. Typescript doesn't reflect unions, intersections, or other kinds of generic types, so there's no returned metadata for this parameter, and if there's no metadata that's actionable Nest will end up skipping the pipe.
You could probably create a custom pipe to do the validation for you, and if you have two types you're probably going to have to. You can still call the appropriate class-transformer and class-validator methods inside of the class too.
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '#nestjs/common';
import { of } from 'rxjs';
#Injectable()
export class CheckTypeInterceptor implements NestInterceptor {
constructor() {}
async intercept(context: ExecutionContext, next: CallHandler) /*: Observable<any>*/ {
const httpContext = context.switchToHttp();
const req = httpContext.getRequest();
const bodyDto = req.body.bodyDto;
// Need Update below logic
if (bodyDto instanceof ClassA || bodyDto instanceof ClassB) {
return next.handle();
}
// Return empty set
return of([]);
}
}
#UseInterceptors(CheckTypeInterceptor)
export class ApiController {
...
}
Encountered a similar situation where I had to validate some union type request. The solution I ended up with was a custom pipe as Jay McDoniel suggested here. The logic would vary depending on the request body you are dealing with, but per the question in case the following may work
Custom pipe:
import { ArgumentMetadata, BadRequestException, Inject, Scope } from "#nestjs/common";
import { PipeTransform } from "#nestjs/common";
import { plainToInstance } from "class-transformer";
import { validate } from "class-validator";
import { ClassADto } from '../repository/data-objects/class-a.dto';
import { ClassBDto } from '../repository/data-objects/class-b.dto';
export class CustomPipeName implements PipeTransform<any> {
async transform(value: any, { metatype, type }: ArgumentMetadata): Promise<any> {
if (type === 'body') {
const classA = plainToInstance(ClassADto, value);
const classB = plainToInstance(ClassBDto, value);
const classAValidationErrors = await validate(classA);
const classBValidationErrors = await validate(classB);
if (classAValidationErrors.length > 0 && classBValidationErrors.length > 0) {
throw new BadRequestException('some fancy info text');
}
}
return value;
}
}
Controller usage:
#Post('/somecall')
public async doSomething(
#Body(new CustomePipeName()) bodyDto: (ClassA | ClassB)
): Promise<any> {
// do something
}

How to get repository in NestJS Interceptor

I had created on Interceptor in the module. I want to get repository [LocumRepository] in the Interceptor and put some processing after the call.
Following is my Intercepter class:
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '#nestjs/common';
import { LocumEntity } from '../../locum/entities/locum.entity';
import { getRepository, Like, Repository } from 'typeorm';
import { Observable, combineLatest } from 'rxjs';
import { tap } from 'rxjs/operators';
import { map } from 'rxjs/operators';
#Injectable()
export class ApprovalInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next
.handle()
.pipe(
map(value => this.updateLocumStatus(value, context))
);
}
async updateLocumStatus(value, context) {
if (context.switchToHttp().getResponse().statusCode) {
let locumData = await getRepository(LocumEntity)
.createQueryBuilder('locum')
.where('locum.id = :id', { id: value.locumId })
.getOne();
}
return value;
}
}
I am receiving following error:
No repository for "LocumRepository" was found. Looks like this entity is not registered in current "default" connection?
while LocumRepository declared in the module file and I am using it out side the Interceptor class
As an interceptor is #Injectable() you could take the DI approach and inject it as you normally would using #InjectRepository(Locum) (or whatever your entity is called), and then do the usual service this.repo.repoMethod(). This way, you also still get the benefits of using DI and being able to provide amock during testing. The only thing to make sure of with this approach is that you have this repository available in the current module where the interceptor will be used.

Is it possible to override global scoped guard with controller/method scoped one

I'm writing webAPI using NestJS framework. I was not able to override global scoped guard with the one placed on method or controller level. All of my endpoints will use JWT verification guard except one used for logging into the system. Is it possible to create one guard on root level and only override this global guard with #UseGuard() decorator on single method level?
I tried to use guard before listen function call and also use APP_GUARDprovider, but in both cases I'm not able to override this behavior.
Code example:
https://codesandbox.io/embed/nest-yymkf
Just to add my 2 cents.
Instead of defining 2 guards (reject and accept) as the OP have done, I have defined a custom decorator:
import { SetMetadata } from '#nestjs/common'
export const NoAuth = () => SetMetadata('no-auth', true)
The reject guard (AuthGuard) uses Reflector to be able to access the decorator's metadata and decides to activate or not based on it.
import { CanActivate, ExecutionContext, Injectable } from '#nestjs/common'
import { Reflector } from '#nestjs/core'
import { Observable } from 'rxjs'
#Injectable()
export class AuthGuard implements CanActivate {
constructor(private readonly reflector: Reflector) {}
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const noAuth = this.reflector.get<boolean>('no-auth', context.getHandler())
if(noAuth) return true
// else your logic here
}
}
I then bind the reject guard globally in some module:
#Module({
providers: [{
provide: APP_GUARD,
useClass: AuthGuard
}]
})
and proceed to use the decorator where needed:
#NoAuth()
#Get() // anyone can access this
getHello(): string {
return 'Hello Stranger!'
}
#Get('secret') // protected by the global guard
getSecret(): string {
return 'ssshhh!'
}
After a posting the question I figured out the solution for my problem. I should add some custom meta-data into my controller and put a logic inside the guard to read that meta-data.
I have updated the code sample.

Resources