In Nest, there is an important and basic concept: Provider, which literally translates “Provider”, but I think it is not accurate enough. I have been thinking about what word can accurately describe the meaning given to it in Nest. Unfortunately, I have limited ability and general level, so I haven’t found it yet.

Repositories (Service, Repositories, Factories, etc.) are repositories. I think the reverse is simple: if we can think of a controller as a function, then anything that serves (is called) a controller can be thought of as a Provider.

No matter from which direction to understand, in short, all kinds of logical services encapsulated in it, unified exposure to the controller to execute. The advantage of this approach is that the business itself can be concerned about the business, other such as: authentication, parameter verification, do not have to consider, just leave it to the middleware. I’m going to dig a hole here, and I’m going to finish the basic function and I’m going to keep writing if you’re interested.

Dependency injection

As mentioned earlier, dependency injection is an IoC implementation pattern, and NEST implements this pattern in depth, leveraging TypeScript’s powerful language (which gives JS the concept of type) to push IoC to a new level in the JS world. By default (creating a Controller on the command line), constructor arguments are injected. We often see code like the following in an application:

@Controller('/')
export class AppController {
  constructor(private readonly appService: AppService) {}
  // If you are not familiar with TS, please note that the above line is equivalent to the following four lines:
  // private readonly appService:AppService;
  // constructor(appService:AppService) {
  // this.appService=appService;
  // }
}
Copy the code

Taking advantage of the TS feature is equivalent to declaring a (class) private read-only AppService object property of type AppService. If you need more than one, you only need to define multiple entry parameters.

Nest also supports property-based injection:

@Controller('/')
export class AppController {
  @Inject('AppService')
  private readonly appService: AppService;
}
Copy the code

In the AppService class, a @Injectable() decorator is needed:

@Injectable(a)export class AppService {
  getHello(): string {
    return 'Hello World! '; }}Copy the code

Because Nest manages the Provider’s instantiation process, developers are generally out of the loop. Therefore, dependency injection that needs to be handled at instantiation time needs to be declared using decorators. The two examples above show how to define constructor injection and property injection using decorators. Note that the injected object is the Controller, the user of the function. Does the Provider itself support being injected? The answer is yes. We’ll come back to this concept when we get down to dynamic modules.

scope

Typically, providers have the same life cycle as applications (they are built one by one based on dependencies, and destroyed when requests are completed). But there are other ways to extend its scope. Nest offers three cycles:

  • DEFAULT: same application cycle;
  • REQUEST: a REQUEST is destroyed after it is processed.
  • TRANSIENT: Each application that uses the Provider gets a separate instance;

Use cases

To illustrate the difference in scope, create a project and then add a controller to the project:

nest new provider
nest g co user
Copy the code

We’ll use this as an example to practice scoping:

@Injectable(a)export class AppService {
  getHello(): string {
    return 'Hello World! ';
  }

  private scopeA = ' ';
  set A(value: string) {
    this.scopeA = value;
  }
  get A() :string {
    return this.scopeA; }}Copy the code

Modify app.controller to define two methods to read and set the A property of the Service:

@Controller(a)export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get('set')
  setString(@Query('str') str: string) :string {
    return this.appService.setA(str);
  }

  @Get('get')
  getString(): string {
    return this.appService.getA(); }}Copy the code

The newly created User. controller performs the same function as the app.controller above:

@Controller('user')
export class UserController {
  constructor(private readonly appService: AppService) {}

  @Get('set')
  setString(@Query('str') str: string) :string {
    return this.appService.setA(str);
  }

  @Get('get')
  getString(): string {
    return this.appService.getA(); }}Copy the code

Testing:

npm run start:dev
curl --location --request GET 'localhost:3000/set? str=Hi~' # done
curl --location --request GET 'localhost:3000/get' # Hi~
curl --location --request GET 'localhost:3000/user/get' # Hi~
Copy the code

At this point, you can see that after Nest is run in singleton mode, one setting can be used in multiple requests. Now, let’s change the scope of the app. Service to a new instance for each injected class:

@Injectable({
  scope: Scope.TRANSIENT,
})
export class AppService {
  getHello(): string {
    return 'Hello World! ';
  }

  private scopeA = ' ';
  set A(value: string) {
    this.scopeA = value;
  }
  get A() :string {
    return this.scopeA; }}Copy the code

Test again:

npm run start:dev
curl --location --request GET 'localhost:3000/set? str=Hi~' # done
curl --location --request GET 'localhost:3000/get' # Hi~
curl --location --request GET 'localhost:3000/user/get' # 
curl --location --request GET 'localhost:3000/user/set? str=Hi User~' # done
curl --location --request GET 'localhost:3000/user/get' # Hi User~
curl --location --request GET 'localhost:3000/get' # Hi~
Copy the code

As you can see, each request successfully writes information, but when the request is repeated, nothing is left. Change the scope of app.service to a new instance for each request:

@Injectable({
  scope: Scope.REQUEST,
})
export class AppService {
  getHello(): string {
    return 'Hello World! ';
  }

  private scopeA = ' ';
  set A(value: string) {
    this.scopeA = value;
  }
  get A() :string {
    return this.scopeA; }}Copy the code

Test again:

npm run start:dev
curl --location --request GET 'localhost:3000/set? str=Hi~' # done
curl --location --request GET 'localhost:3000/get' # 
curl --location --request GET 'localhost:3000/user/get' # 
curl --location --request GET 'localhost:3000/user/set? str=Hi User~' # done
curl --location --request GET 'localhost:3000/user/get' # 
Copy the code

The scope chain

The concept of scope is not limited to Provider applications, but also to controllers. Since dependency injection is applied throughout the architecture, the scope of the dependent will be equal to the scope of the dependent. Remember the constructor injection above? It takes a new instance to handle the new constructor. For example, if a Service is request-scoped, the Controller that uses it is request-scoped.

@Controller(a)export class AppController {
  constructor(private readonly appService: AppService) {}

  private B = ' ';

  @Get('set')
  setString(@Query('str') str: string) :string {
    this.appService.A = str;
    this.B = str;
    return 'done';
  }

  @Get('get')
  getString(): string {
    console.log(this.B);
    return this.appService.A; }}Copy the code

Added a B field to the controller, and the requested console outputs a blank line. Remove the scope parameter for app.service (the default is app scope) and you can see the console output a result.

The registered Provider

Even a complex dependency will ultimately have to be handled by Nest’s IoC strategy. Place all referenced files in the Module file and register them with the @Module decorator.