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.
Related
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
Before upgrading to typeorm 0.3 I could use getConnection().getRepository<User>(User) in my guard to get a repo for a type and operate on it.
With 0.3 however that is deprecated (see also https://newreleases.io/project/github/typeorm/typeorm/release/0.3.0) and now I cannot get access to the db in my guard anymore. I tried to use
#InjectRepository(User)
private userRepo: Repository<User>,
in the guard's constructor and then tried to make the guard a provider from a module that I exported but also that didnt work.
So I wonder how to get access to a repo or connection there. Otherwise I would probably need to pass my connection details to the Guard and create a new connecion ther which seems aweful.
You can try with mixin https://wanago.io/2021/12/13/api-nestjs-mixin-pattern/.
Check the section Passing additional arguments.
I have done it with DataSource you can use Repository as well.
//scope.guard.ts
import { CanActivate, ExecutionContext, NotFoundException, Inject, Type, mixin } from "#nestjs/common";
import { DataSource, getRepository, ObjectType } from "typeorm";
import { Request } from "express";
const ScopeGuard = (entityClass): Type<CanActivate> => {
class ScopeGuardMixin {
constructor(#Inject(DataSource) private readonly dataSource: DataSource) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
console.log("here 1");
console.log(entityClass);
console.log("here 2");
const request = context.switchToHttp().getRequest<Request>();
console.log(await this.dataSource.getRepository(entityClass).findOneBy({ id: Number(request.params.id) }));
return true;
}
}
return mixin(ScopeGuardMixin);
};
export default ScopeGuard;
Controller code
import ScopeGuard from "app/modules/guards/scope.guard";
#UseGuards(ScopeGuard(User))
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.
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)
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.