In NestJS, how to get execution context or request instance in custom method decorator?
Asked Answered
P

3

7

I have a custom method decorator like this.

export function CustomDecorator() {

    return applyDecorators(
        UseGuards(JwtAuthGuard)
    );
}

Inside the Custom Decorator, I want to get the Request Header but not sure how to get the Request Instance?

Popular answered 19/8, 2020 at 23:42 Comment(2)
Could add more info about what do you wanna do, please? I got it that you want to access headers but with what purpose?Gustatory
We have a shared auth module in which we can have JWT or GoogleAuth. I want to implement a custom decorator which applies different guards based on the request headers.Popular
H
12

You won't be able to get the ExectuionContext object or the Request object in a class or method decorator, because these decorators are run immediately at the moment of import. What should be done instead is to make a SuperGuard that does have the ExecutionContext available to it. This SuperGuard should have all of the other guards injected into it via the constructor and depending on the header you should call/return the result from the guard called. Something like this:

@Injectable()
export class SuperGuard implements CanActivate {
  constructor(
    private readonly jwtAuthGuard: JwtAuthGuard,
    private readonly googleAuthGuard: GoogleAuthGuard,
  ) {}

  canActivate(context: ExecutionContext) {
    const req = context.switchToHttp().getRequest();
    if (req.headers['whatever'] === 'google') {
      return this.googleAuthGuard.canActivate(context);
    } else {
      return this.jwtAuthGuard.canActivate(context);
    }
  }
}
Haleakala answered 20/8, 2020 at 3:37 Comment(5)
Hi Jay, I get this error. Error: Nest can't resolve dependencies of the RomeGuard (?). Please make sure that the argument JwtAuthGuard at index [0] is available in the AuthModule context. Potential solutions: - If JwtAuthGuard is a provider, is it part of the current AuthModule? - If JwtAuthGuard is exported from a separate @Module, is that module imported within AuthModule? @Module({ imports: [ /* the Module containing JwtAuthGuard */ ] }) at Injector.lookupComponentInParentModulesPopular
You'll need to make sure that wherever you use SuperGuard you have JwtAuthGuard and GoogleAuthGuard added as providers. This can be done from a GuardModule if you so chooseHaleakala
Hi Jay, I added it like this providers: [SharedDataAuthService, JwtStrategy, JwtAuthGuard], but it's showing that error.Popular
All good Jay. I need to add it into exports as well. Thanks for your help.Popular
Hi Jay, I have a question... If the Guard decorator run immediately at the moment of import, how could it catch the request in executionContext ? ^^Lorenelorens
A
0

I managed to access the execution context within decorator using Inject inside decorator's factory. Here is my decorator that swallows errors produced by method and returns predefined value in case of exception.

import { Injectable, Scope, Inject, ExecutionContext } from '@nestjs/common';
import { CONTEXT } from '@nestjs/graphql';

@Injectable({ scope: Scope.REQUEST })
export class ExceptionsHandler {
  public constructor(@Inject(CONTEXT) private readonly context: ExecutionContext) {}

  private integrationsRequestErrors: unknown[] = [];

  public handle(error: unknown): void {
    // ADD error to context if necessary
    this.integrationsRequestErrors.push(error);
  }
}

export const ErrorSwallower = (options: {
  serviceImplementation: string;
  defaultValue: unknown;
  errorMessage?: string;
}): MethodDecorator => {
  const { defaultValue, integration } = options;
  const Injector = Inject(ExceptionsHandler);
  return (target: object, _propertyKey: string, descriptor: PropertyDescriptor) => {
    Injector(target, 'exceptionsHandler');
    const originalMethod = descriptor.value;
    descriptor.value = function (...args: unknown[]) {
      const exceptionHandler = this.experiment as ExceptionsHandler;
      try {
        const result = originalMethod.apply(this, args);
        if (result && result instanceof Promise) {
          return result.catch((error: unknown) => {
            exceptionHandler.handle({ error, integration });
            return defaultValue;
          });
        }
        return result;
      } catch (error) {
        exceptionHandler.handle({ error, integration });
        return defaultValue;
      }
    };
  };
};

and here is the code above put into action:

@Injectable()
export class ExampleService {
  @ErrorSwallower({ serviceImplementation: 'ExampleClass', defaultValue: [] })
  private async getSomeData(args: IGetSomeDataArgs): Promise<ISomeData[]> {
    throw new Error('Oops');
  }
}

Accommodation answered 4/1, 2023 at 21:21 Comment(0)
N
-1
  1. I get the context in the resolver constructor (if the decorator is used in the resolver).
constructor(@Inject(CONTEXT) private readonly context: ExecutionContext) {}
  1. In the method decorator, I read the request context from the method execution context.
export function CustomDecorator() {
    return applyDecorators(
        UseGuards(JwtAuthGuard)
        const context = this.context;
    );
}
Nebulize answered 9/4 at 9:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.