Providers

Providers are a fundamental concept in Nest. Many of the basic Nest classes may be treated as a provider – services, repositories, factories, helpers, and so on. The main idea of a provider is that it can inject dependencies; this means objects can create various relationships with each other, and the function of “wiring up” instances of objects can largely be delegated to the Nest runtime system. A provider is simply a class annotated with an @Injectable() decorator.

Providers - 图1

In the previous chapter, we built a simple CatsController. Controllers should handle HTTP requests and delegate more complex tasks to providers. Providers are plain JavaScript classes with an @Injectable() decorator preceding their class declaration.

info Hint Since Nest enables the possibility to design and organize dependencies in a more OO-way, we strongly recommend following the SOLID principles.

Services

Let’s start by creating a simple CatsService. This service will be responsible for data storage and retrieval, and is designed to be used by the CatsController, so it’s a good candidate to be defined as a provider. Thus, we decorate the class with @Injectable().

  1. @@filename(cats.service)
  2. import { Injectable } from '@nestjs/common';
  3. import { Cat } from './interfaces/cat.interface';
  4. @Injectable()
  5. export class CatsService {
  6. private readonly cats: Cat[] = [];
  7. create(cat: Cat) {
  8. this.cats.push(cat);
  9. }
  10. findAll(): Cat[] {
  11. return this.cats;
  12. }
  13. }
  14. @@switch
  15. import { Injectable } from '@nestjs/common';
  16. @Injectable()
  17. export class CatsService {
  18. constructor() {
  19. this.cats = [];
  20. }
  21. create(cat) {
  22. this.cats.push(cat);
  23. }
  24. findAll() {
  25. return this.cats;
  26. }
  27. }

info Hint To create a service using the CLI, simply execute the $ nest g service cats command.

Our CatsService is a basic class with one property and two methods. The only new feature is that it uses the @Injectable() decorator. The @Injectable() decorator attaches metadata, which tells Nest that this class is a Nest provider. By the way, this example also uses a Cat interface, which probably looks something like this:

  1. export interface Cat {
  2. name: string;
  3. age: number;
  4. breed: string;
  5. }

Now that we have a service class to retrieve cats, let’s use it inside the CatsController:

  1. @@filename(cats.controller)
  2. import { Controller, Get, Post, Body } from '@nestjs/common';
  3. import { CreateCatDto } from './dto/create-cat.dto';
  4. import { CatsService } from './cats.service';
  5. import { Cat } from './interfaces/cat.interface';
  6. @Controller('cats')
  7. export class CatsController {
  8. constructor(private readonly catsService: CatsService) {}
  9. @Post()
  10. async create(@Body() createCatDto: CreateCatDto) {
  11. this.catsService.create(createCatDto);
  12. }
  13. @Get()
  14. async findAll(): Promise<Cat[]> {
  15. return this.catsService.findAll();
  16. }
  17. }
  18. @@switch
  19. import { Controller, Get, Post, Body, Bind, Dependencies } from '@nestjs/common';
  20. import { CatsService } from './cats.service';
  21. @Controller('cats')
  22. @Dependencies(CatsService)
  23. export class CatsController {
  24. constructor(catsService) {
  25. this.catsService = catsService;
  26. }
  27. @Post()
  28. @Bind(Body())
  29. async create(createCatDto) {
  30. this.catsService.create(createCatDto);
  31. }
  32. @Get()
  33. async findAll() {
  34. return this.catsService.findAll();
  35. }
  36. }

The CatsService is injected through the class constructor. Notice the use of the private readonly syntax. This shorthand allows us to both declare and initialize the catsService member immediately in the same location.

Dependency injection

Nest is built around the strong design pattern commonly known as Dependency injection. We recommend reading a great article about this concept in the official Angular documentation.

In Nest, thanks to TypeScript capabilities, it’s extremely easy to manage dependencies because they are resolved just by type. In the example below, Nest will resolve the catsService by creating and returning an instance of CatsService (or, in the normal case of a singleton, returning the existing instance if it has already been requested elsewhere). This dependency is resolved and passed to your controller’s constructor (or assigned to the indicated property):

  1. constructor(private readonly catsService: CatsService) {}

Scopes

Providers normally have a lifetime (“scope”) synchronized with the application lifecycle. When the application is bootstrapped, every dependency must be resolved, and therefore every provider has to be instantiated. Similarly, when the application shuts down, each provider will be destroyed. However, there are ways to make your provider lifetime request-scoped as well. You can read more about these techniques here.

Custom providers

Nest has a built-in inversion of control (“IoC”) container that resolves relationships between providers. This feature underlies the dependency injection feature described above, but is in fact far more powerful than what we’ve described so far. The @Injectable() decorator is only the tip of the iceberg, and is not the only way to define providers. In fact, you can use plain values, classes, and either asynchronous or synchronous factories. More examples are provided here.

Optional providers

Occasionally, you might have dependencies which do not necessarily have to be resolved. For instance, your class may depend on a configuration object, but if none is passed, the default values should be used. In such a case, the dependency becomes optional, because lack of the configuration provider wouldn’t lead to errors.

To indicate a provider is optional, use the @Optional() decorator in the constructor’s signature.

  1. import { Injectable, Optional, Inject } from '@nestjs/common';
  2. @Injectable()
  3. export class HttpService<T> {
  4. constructor(
  5. @Optional() @Inject('HTTP_OPTIONS') private readonly httpClient: T
  6. ) {}
  7. }

Note that in the example above we are using a custom provider, which is the reason we include the HTTP_OPTIONS custom token. Previous examples showed constructor-based injection indicating a dependency through a class in the constructor. Read more about custom providers and their associated tokens here.

Property-based injection

The technique we’ve used so far is called constructor-based injection, as providers are injected via the constructor method. In some very specific cases, property-based injection might be useful. For instance, if your top-level class depends on either one or multiple providers, passing them all the way up by calling super() in sub-classes from the constructor can be very tedious. In order to avoid this, you can use the @Inject() decorator at the property level.

  1. import { Injectable, Inject } from '@nestjs/common';
  2. @Injectable()
  3. export class HttpService<T> {
  4. @Inject('HTTP_OPTIONS')
  5. private readonly httpClient: T;
  6. }

warning Warning If your class doesn’t extend another provider, you should always prefer using constructor-based injection.

Provider registration

Now that we have defined a provider (CatsService), and we have a consumer of that service (CatsController), we need to register the service with Nest so that it can perform the injection. We do this by editing our module file (app.module.ts) and adding the service to the providers array of the @Module() decorator.

  1. @@filename(app.module)
  2. import { Module } from '@nestjs/common';
  3. import { CatsController } from './cats/cats.controller';
  4. import { CatsService } from './cats/cats.service';
  5. @Module({
  6. controllers: [CatsController],
  7. providers: [CatsService],
  8. })
  9. export class AppModule {}

Nest will now be able to resolve the dependencies of the CatsController class.

This is how our directory structure should look now:

src
cats
dto
create-cat.dto.ts
interfaces
cat.interface.ts
cats.service.ts
cats.controller.ts
app.module.ts
main.ts