Passing a service to an interceptor in Nestjs - nestjs

I am using Nestjs and want to pass a service to the interceptor with generic types but I am getting errors that Nest cannot resolve the dependencies for the interceptor.
Here is an example:
Here is the interceptor:
#Injectable()
export class BaseInterceptor<Entity, SomeService> implements NestInterceptor {
constructor(public Service: SomeService) {}
intercept(context: ExecutionContext, next: CallHandler) {
const request = context.switchToHttp().getRequest();
const body: Entity = request.body;
if(body instanceof CreateProductDto) {
console.log("product!");
// do stuff with the service here
}
return next.handle().pipe();
}
}
Here is a class extending this:
#Injectable()
export class ProductsInterceptor extends BaseInterceptor<CreateProductDto, ProductsService> {
constructor() {
super(new ProductsService());
}
}
And I am calling it on a route:
#UseInterceptors(new ProductsInterceptor())
#Post()
....
My ultimate goal is to be able to use this interceptor on any route and then define some actions in the interceptor based on the Dto that I receive. I believe the main issue here is how to manage the dependencies for the interceptor within Nest which is what i am not sure about.

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

Is it possible to intercept calls from one controller to another one in Nestjs?

and I'm working in a backend application and I want to reuse some api writed before in other controller but its necesary that when I call a method for example from controller A to Controller B it must be intecepted by a Guard, middleware, etc. I'm using a global guard, that intercept any request call. And I tried something like the example below but just intercept the first call triggered on controller A but when call to controller B it dosent trigger
#Controller('controller-a')
export class ControllerA {
#Get()
methodA(){
const respFromB = await ControllerB.prototype.methodB({ ..some data.. });
enter code here
return '...'
}
}
#Controller('controller-b')
export class ControllerB {
#Post()
methodB(
#Body() data: any
) {
... some other code...
return 'books';
}
}
// main.ts
const reflector = app.get(Reflector);
const authService = app.get(AuthService);
const prismaClient = app.get(PrismaClient);
app.useGlobalGuards(new MyGlobalGuard(reflector, authService, prismaClient));
// MyGlobalGuard.ts
#Injectable()
export class MyGlobalGuard implements CanActivate {
public constructor(
private readonly reflector: Reflector,
private readonly authService: AuthService,
private readonly prisma: PrismaClient,
) {}
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const request = context.switchToHttp().getRequest();
....
return true;
}
}
As you're the one calling from one controller to another, no it's not possible. You'd need to make an HTTP request from your server to your server to trigger the guards and interceptors again. It's Nest's internal route handler that's in charge of calling these enhancers, so you can't get to them from directly calling the class

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.

How to ignore some routes from #UseGuards() in a controller?

I have a controller like this:
#ApiBearerAuth()
#UseGuards(AuthGuard('jwt'))
#ApiTags('books')
#Controller('books')
export class BooksController {
#Post()
async create(#Body() createBookVm: CreateBookVm) {
//........
}
#Get()
async all() {
//........
}
}
When I access all() rout in above controller without accessToken I get the foloowing error:
{"statusCode":401,"error":"Unauthorized"}
It is a correct behavior but I want ignore all() action from general #UseGuards of the controller. I want access it as a public rout without authorization.
The easiest way is to change Guards to routes:
#ApiBearerAuth()
#ApiTags('books')
#Controller('books')
export class BooksController {
#Post()
#UseGuards(AuthGuard('jwt'))
async create(#Body() createBookVm: CreateBookVm) {
//........
}
#Get()
async all() {
//........
}
}
To provide another answer, albeit one that requires more code, is you could create a custom decorator that assigns metadata to the class and/or the class method. This metadata, in theory, would be for telling the guard to skip the auth check on this entire class, or on this route (depending on how you set the metadata up), and return true so that the request can still flow.
I've got a decorator like this set up here that sets up metadata if you'd like to take a look at how it works.
With this kind of approach, you could bind the guard globally, and then add the #AuthSkip() (or whatever you call it) decorator to the routes or classes you don't want to authorize.
Now you'll need to extend the AuthGuard('jwt') and update the canActivate() method to check for this metadata in the current context. This means that you'll need to add the Reflector as a dependency to the guard class and use it to get the metadata from both the class and the current route (if you went so far as to make it work for ignoring classes and not just routes), and if the metadata exists, then the route was to be skipped, return true from the guard. I make that kind of check here if you'd like to see an example of that in action.
Assuming you have used the app.useGlobalGuards() method inside main.ts file, add the following code inside the auth.guard.ts file:
import { ExecutionContext, Injectable } from '#nestjs/common';
import { Reflector } from '#nestjs/core';
import { AuthGuard as PassportAuthGaurd } from '#nestjs/passport';
#Injectable()
export class AuthGuard extends PassportAuthGaurd('jwt') {
constructor(private readonly reflector: Reflector) {
super();
}
canActivate(context: ExecutionContext) {
const isPublic = this.reflector.get<boolean>(
'isPublic',
context.getHandler()
);
if (isPublic) {
return true;
}
return super.canActivate(context);
}
}
I had used pssport jwt method here, but you can alter it according to you, just remember to keep constructor and the logic of canActivate same.
Now in your main.ts modify global guard so we can use Reflectors in it:
const reflector = app.get(Reflector);
app.useGlobalGuards(new AuthGuard(reflector));
Now in order to make routes public we would use a custom decorator, for that create a file named public.decorator.ts and add the following code:
import { SetMetadata } from '#nestjs/common';
export const Public = () => SetMetadata('isPublic', true);
Here we have added a custom metadata value which is same value that we used inside our auth.guard.ts file. Now just add this #Public() decorator on the route that you want to make public:
#Get()
#Public()
async all() {
//........
}
Now your all function won't check for the token authentication.
I found this blog which does the same thing, you can check it out.

Access Nest "injector" in a custom interceptor

I need to access a service (provided by Nest TypeOrmModule) inside the intercept function (important note: not as constructor parameter!!!) because it depends of the passed options (entity in this case).
The service injection token is provided by the getRepositoryToken function.
export class PaginationInterceptor {
constructor(private readonly entity: Function) {}
intercept(context: ExecutionContext, call$: Observable<any>): Observable<any> {
// Here I want to inject the TypeORM repository.
// I know how to get the injection token, but not HOW to
// get the "injector" itself.
const repository = getRepositoryToken(this.entity);
// stuff...
return call$;
}
}
Is any concept of "service container" in Nest? This github issue didn't help me.
Example usage (controller action):
#Get()
#UseInterceptors(new PaginationInterceptor(Customer))
async getAll() {
// stuff...
}
Regarding dependency injection (if you really want/need it), I guess using a mixin class can do the trick. See the v4 documentation (Advanced > Mixin classes).
import { NestInterceptor, ExecutionContext, mixin, Inject } from '#nestjs/common';
import { getRepositoryToken } from '#nestjs/typeorm';
import { Observable } from 'rxjs';
import { Repository } from 'typeorm';
export function mixinPaginationInterceptor<T extends new (...args: any[]) => any>(entityClass: T) {
// declare the class here as we can't give it "as-is" to `mixin` because of the decorator in its constructor
class PaginationInterceptor implements NestInterceptor {
constructor(#Inject(getRepositoryToken(entityClass)) private readonly repository: Repository<T>) {}
intercept(context: ExecutionContext, $call: Observable<any>) {
// do your things with `this.repository`
return $call;
}
}
return mixin(PaginationInterceptor);
}
Disclaimer: this is valid TypeScript code but I didn't had the chance to test it in a real project, so it might need a bit of rework. The idea is to use it like this:
#UseInterceptors(mixinPaginationInterceptor(YourEntityClass))
Let me know if you have any question about the code. But I think the doc about mixin is pretty good!
OR You can also use getRepository from typeorm (passing the entity class). This is not DI, thus, it will oblige you to spyOn the getRepository function in order to do proper testing.
Regarding the container, I'm almost sure that the only way to access it is using the Execution Context, as pointed by Kim.

Resources