In Nest.Js, How to Get a Service Instance Inside a Decorator

In nest.js, is it possible to get service instance inside a param decorator?

It is not possible to inject a service into your custom decorator.

Instead, you can create an AuthGuard that has access to your service. The guard can then add a property to the request object, which you can then access with your custom decorator:

@Injectable()
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService) {}

async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const bearerToken = request.header.Authorization;
const user = await this.authService.authenticate(bearerToken);
request.principal = user;
// If you want to allow the request even if auth fails, always return true
return !!user;
}
}
import { createParamDecorator } from '@nestjs/common';

export const Principal = createParamDecorator((data: string, req) => {
return req.principal;
});

and then in your controller:

@Get()
@UseGuards(AuthGuard)
get(@Principal() principal: Principal) {
// ...
}

Note that nest offers some standard modules for authentication, see the docs.

NestJs - Calling a method from a service inside a Custom Decorator

Its unclear if you are using the NestJS cache-manager wrapper, if so you need to use the provider token when injecting and the type would be Cache.

export function ClearCache() {

const injector = Inject(CACHE_MANAGER)

return (target: any, _key?: string | symbol, descriptor?: TypedPropertyDescriptor<any>) => {

injector(target, 'cacheManager')

const originalMethod = descriptor.value

descriptor.value = async function (...args: any[]) {
try {
const service: Cache = this.cacheManager
service.delete(args[1])
return await originalMethod.apply(this, args)
} catch (err) {
console.error(err)
return originalMethod(args)
}
}
}
}

If it is a different service, ensure that you are using the correct provider token, it is in the current modules scope and instantiated. If it isn't in current module, check it is global

Uploaded minimal repo of above code here.

Update:

tsconfig.json has "strict":true. If it is enabled, then "this" would be strictly use the PropertyDescriptor interface.

Fixed the issue by changing "strict": false in the tsconfig.json.

Use global nest module in decorator

Okay, found a solution. In case anyone else stumbles upon this. First please keep in mind how decorators work – they are class constructor based, not instance based.

In my case I wanted to have my logger service injected in the class instance. So the solution is to tell Nest in the decorator to inject the LoggerService into the instance of the class that contains the decorated method.

import { Inject } from '@nestjs/common';
import { LoggerService } from '../../logger/logger.service';

export function logErrorDecorator(bubble = true) {
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;
}
}
};
};
}

This gives the possibility to catch errors in a method, log them within the service context, and either re-throw them (so your controllers can handle user resp) or not. In my case I also had to implement some transaction-related logic here.

export class FoobarService implements OnModuleInit {
onModuleInit() {
this.test();
}

@logErrorDecorator()
test() {
throw new Error('Oh my');
}
}

How to integrate dependency injection with custom decorators

You should consider using an Interceptor instead of a custom decorator as they run earlier in the Nest pipeline and support dependency injection by default.

However, because you want to both pass values (for cache timeout) as well as resolve dependencies you'll have to use the mixin pattern.

import {
ExecutionContext,
Injectable,
mixin,
NestInterceptor,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { TestService } from './test/test.service';

@Injectable()
export abstract class CacheInterceptor implements NestInterceptor {
protected abstract readonly cacheDuration: number;

constructor(private readonly testService: TestService) {}

intercept(
context: ExecutionContext,
call$: Observable<any>,
): Observable<any> {
// Whatever your logic needs to be

return call$;
}
}

export const makeCacheInterceptor = (cacheDuration: number) =>
mixin(
// tslint:disable-next-line:max-classes-per-file
class extends CacheInterceptor {
protected readonly cacheDuration = cacheDuration;
},
);

You would then be able to apply the Interceptor to your handler in a similar fashion:

@Injectable()
class UserService{
@UseInterceptors(makeCacheInterceptor(1000))
async getUser(id:string):Promise<User>{
// Make a call to db to get all Users
}
}

Access an injected service from within a Class decorator? Typescript / NestJS

Based on the official class-decorator docs, here's a way that should work:

function CustDec<T extends new(...args: any[]) => {}>(Target: T) {
return class extends Target {
constructor(...args: any[]) {
super(...args);
(args[0] as InjectedService).doSomething();
}
}
}

@CustDec
@Injectable()
class MyService {
constructor(
private readonly injectedService: InjectedService,
) {}
}

I've also created an example on TS-Playground.



Related Topics



Leave a reply



Submit