Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to inject a request scoped provider at NestJS controller?

I have a request scoped injectable for logging.

f.e.

import { Injectable, Scope } from '@nestjs/common';

@Injectable({ scope: Scope.REQUEST })
export class RequestLogger {
  public log(message: string) {
    console.log(message);
  }
}

(disregard for a moment that it doesn't use a constructor with the request yet; that's beside the point)

And I also have a controller with some singleton injections.

I'd like to inject the request injectable in a way that will allow the controller to be initiated just once. Injecting it at the constructor will make the controller be recreated on each request, so surely that's not the way (is it?).

I tried to place it in the method signature, but that doesn't seem to do anything.

e.g.

@Controller('register')
export class RegisterApiController {
  public constructor(
    private readonly registerService: RegisterService,
  ) {
    console.log('Controller initiated');
  }

  @Post()
  public async postIndex(
    @Inject(RequestLogger) logger: RequestLogger,
  ): Promise<unknown> {
    console.log('request made');
    logger.log('Logger message to log');

    return this.registerService.register();
  }
}

after application bootstrap (that also includes "Controller initiated"), each request terminates with error 500, and in the console

Request made
[TypeError] Cannot read property 'log' of undefined

Is there a way to inject a request scoped injectable without forcing a recreation of the controller that uses it? What is it?

If there isn't another way, is there at least a way to move initial controller logic somewhere else, so that what needs to be done once on first controller init can be done there?

like image 885
boen_robot Avatar asked Mar 03 '23 23:03

boen_robot


2 Answers

This is possible with the library I've created recently, which it's free from bubbles up injection chain and performance issues:

https://github.com/kugacz/nj-request-scope

After register RequestScopeModule in your module class:

import { RequestScopeModule } from 'nj-request-scope';

@Module({
    imports: [RequestScopeModule],
})

your example request-scope RequestLogger class would look like this:

import { Injectable, Scope } from '@nestjs/common';
import { RequestScope } from 'nj-request-scope';

@Injectable()
@RequestScope()
export class RequestLogger {
  public log(message: string) {
    console.log(message);
  }
}

and in your example controller, add the class field logger.
The logger field will be a new RequestLogger instance created for every request without recreating a new instance of RegisterApiController.

@Controller('register')
export class RegisterApiController {
  public constructor(
    private readonly registerService: RegisterService,
    private readonly logger: RequestLogger,
  ) {
    console.log('Controller initiated');
  }

  @Post()
  public async postIndex(): Promise<unknown> {
    console.log('request made');
    logger.log('Logger message to log');

    return this.registerService.register();
  }
}

Another example you can find here: https://github.com/kugacz/nj-request-scope-example/tree/main/src/request.scope

like image 93
profes Avatar answered Mar 05 '23 16:03

profes


With nestjs, unfortunately, this is not possible, see the docs:

Scope bubbles up the injection chain. A controller that depends on a request-scoped provider will, itself, be request-scoped.


You can, however, inject the initial controller logic into the controller as a singleton, so that it won't be executed again for every request.

like image 40
Kim Kern Avatar answered Mar 05 '23 16:03

Kim Kern