I am trying to create an interceptor for audit logging. Here I want to get previous data if the request is PUT or PATCH, so I want to access different services in my app to get the document before processing.
The problem is that I am trying to access the context class in the interceptor to get the service associated with the context class, but it is not possible to access the service. I could access the static variable, is there anyway to access non-static members such as service inside the interceptor by using getContext()
?
@Injectable()
export class AuditLoggingInterceptor implements NestInterceptor {
private readonly logger = new Logger(AuditLoggingInterceptor.name);
constructor(
private readonly auditlogService: AuditLogService,
private readonly reflector: Reflector,
private readonly moduleRef: ModuleRef,
) {}
async intercept(
context: ExecutionContext,
next: CallHandler,
): Promise<Observable<any>> {
const req = context?.switchToHttp()?.getRequest();
const res = context?.switchToHttp()?.getResponse();
const ctx: any = context.getClass();
console.log(ctx, ctx.prototype, ctx.test, 'testing');
console.log(ctx.chec );
// how to access service inside class?
return next.handle().pipe(
tap(async (data) => {
return data;
}),
);
}
}
To my knowledge, if the service is private
in the controller, you won't be able to access it directly (if it's public you could use ModuleRef.get(controller).service
), but you can use some clever tricks and reflection to get what you're looking for. I was able to contrive a non-type safe example that should probably be hacked upon to be more stable, but the idea is to get the design:paramtypes
metadata from the controller, which should be the injected providers, and then find the class that has the same prefix as the current controller with the Service
postfix to it, and then use ModuleRef
to grab it from the DI container.
import {
CallHandler,
ExecutionContext,
Injectable,
NestInterceptor,
} from '@nestjs/common';
import { ModuleRef, Reflector } from '@nestjs/core';
import { Observable } from 'rxjs';
@Injectable()
export class AppInterceptor implements NestInterceptor {
constructor(
private readonly moduleRef: ModuleRef,
private readonly reflector: Reflector,
) {}
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const featureName = context.getClass().name.replace('Controller', '');
const controllerDeps = Reflect.getMetadata(
'design:paramtypes',
context.getClass(),
);
const serviceRef = controllerDeps.find((dep) => {
return dep.name?.match(featureName + 'Service');
});
const service = this.moduleRef.get(serviceRef, { strict: false });
console.log(service.getHello());
return next.handle();
}
}
I did this with a simple nest new
project and the AppController
and AppService
, but the idea should extend out to anything you need it to. Please make sure to add type safety to this and use it only where necessary.