NestJS lesson 3: 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.

In the previous chapter, we built a simple ControllerNameController. 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.

Services

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

D:\TypeScript\Nestjs\project1>nest g service controller-name
CREATE src/controller-name/controller-name.service.spec.ts (517 bytes)
CREATE src/controller-name/controller-name.service.ts (98 bytes)
UPDATE src/app.module.ts (470 bytes)

Default content controller-name/controller-name.service.ts

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

@Injectable()
export class ControllerNameService {}

Create an interface interfaces/category.interface.ts

export interface category {
    id?: string
    name: string
    description: string
}

Now we can create a fully example of file controller-name/controller-name.service.ts

import { Injectable } from '@nestjs/common';
import { category } from '../interfaces/category.interface'

@Injectable()
export class ControllerNameService {
    private readonly categories: category[] = [];

    create(category: category) {
        this.categories.push(category);
    }

    findAll(): category[] {
        return this.categories;
    }
}

Our ControllerNameService 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.

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

import { Controller, Get, Post, Body } from '@nestjs/common';
import { ControllerNameService } from './controller-name.service';
import { category } from '../interfaces/category.interface';

@Controller({
    path: 'controller-name'
})
export class ControllerNameController {
    constructor(private controllerNameService: ControllerNameService) {}
 
    @Post()
    async create(@Body() categoryDto: category) {
        this.controllerNameService.create(categoryDto);
    }
 
    @Get()
    async findAll(): Promise<category[]> {
        return this.controllerNameService.findAll();
    }
}

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

Run test:

$ curl -H "Content-Type: application/json" -X "POST" -d '{"id":1,"name":"category 1","description":""}' http://127.0.0.1:3000/controller-name
$ curl http://127.0.0.1:3000/controller-name
[{"id":1,"name":"category 1","description":""}]

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 controllerNameService by creating and returning an instance of ControllerNameService (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):

constructor(private controllerNameService: ControllerNameService) {}

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.

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

@Injectable()
export class HttpService<T> {
  constructor(@Optional() @Inject('HTTP_OPTIONS') private httpClient: T) {}
}

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.

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

@Injectable()
export class HttpService<T> {
  @Inject('HTTP_OPTIONS')
  private readonly httpClient: T;
}

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 (ControllerNameService), and we have a consumer of that service (ControllerNameController), 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.

app.module.ts

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ControllerNameController } from './controller-name/controller-name.controller';
import { ControllerNameService } from './controller-name/controller-name.service';

@Module({
  imports: [],
  controllers: [AppController, ControllerNameController],
  providers: [AppService, ControllerNameService],
})
export class AppModule {}

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

This is how our directory structure should look now:

src
 |--controller-name
   |--controller-name.controller.spec.ts
   |--controller-name.controller.ts
   |--controller-name.service.spec.ts
   |--controller-name.service.ts
 |--interfaces
   |--category.interface.ts
 |--app.controller.spec.ts
 |--app.controller.ts
 |--app.module.ts
 |--app.service.ts

Manual instantiation
Thus far, we’ve discussed how Nest automatically handles most of the details of resolving dependencies. In certain circumstances, you may need to step outside of the built-in Dependency Injection system and manually retrieve or instantiate providers. We briefly discuss two such topics below.

To get existing instances, or instantiate providers dynamically, you can use Module reference.

To get providers within the bootstrap() function (for example for standalone applications without controllers, or to utilize a configuration service during bootstrapping) see Standalone applications.


Documentation @nestjs/core
Documentation @nestjs/common
Documentation @nestjs/websockets
Documentation @nestjs/microservices

Leave a Reply