How to Use Dependency Injection in NestJS

Updated: January 1, 2024 By: Guest Contributor Post a comment

Introduction

Dependency Injection (DI) is a powerful design pattern that NestJS harnesses to create loosely coupled, maintainable, and scalable applications. This tutorial will guide you through the process of using DI in NestJS, starting with the basics and progressing to more advanced concepts.

Basic DI in NestJS

In NestJS, providers such as services, repositories, or custom providers can be injected into controllers or other services. Let’s start with a simple example:

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

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

import { Controller, Get } from '@nestjs/common';
import { MyService } from './my.service';

@Controller('hello')
export class MyController {
  constructor(private myService: MyService) {}

  @Get()
  getHello(): string {
    return this.myService.getHello();
  }
}

This is dependency injection at its simplest in NestJS:

  1. Create a service marked with the @Injectable() decorator.
  2. Inject the service into the constructor of a controller or another service.
  3. Use the injected service within the class methods.

Custom Providers

Sometimes you may need to provide a custom implementation or value. Here’s how you can use custom providers:

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

const CUSTOM_VALUE = 'CUSTOM_VALUE';

@Injectable()
export class SomeService {
  constructor(@Inject(CUSTOM_VALUE) private value: string) {}

  getValue(): string {
    return this.value;
  }
}

@Module({
  providers: [
    {
      provide: CUSTOM_VALUE,
      useValue: 'This is a custom value',
    },
    SomeService,
  ],
})
export class SomeModule {}

In the above code, we are using the useValue syntax to inject a constant value.

Using Class Providers

Class providers allow you to map abstract classes or interfaces to concrete implementations:

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

interface IExampleService {
  doSomething(): void;
}

@Injectable()
export class ExampleService implements IExampleService {
  public doSomething(): void {
    // Implementation here
  }
}

@Module({
  providers: [{ provide: 'IExampleService', useClass: ExampleService }],
})
export class ExampleModule {}

Asynchronous Providers

What if you need to perform some asynchronous operation before a provider is available? That’s where useFactory and the async keyword come in.

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

@Injectable()
export class AsyncService {
  async getValueAsync(): Promise {
    return 'Async Value';
  }
}

@Module({
  providers: [
    {
      provide: 'ASYNC_VALUE',
      useFactory: async (): Promise => {
        const asyncService = new AsyncService();
        return await asyncService.getValueAsync();
      },
    },
  ],
})
export class AppModule {}

Here the ASYNC_VALUE token is associated with a Promise resolved to a string, retrieved from an asynchronous service function.

Property-based Injection

Although constructor-based injection is more prevalent and recommended in NestJS, property-based injection can occasionally be useful. Below is an example of how it’s done:

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

@Injectable()
export class PropertyBasedService {
  @Inject('MY_VALUE')
  private readonly value: string;

  getValue(): string {
    return this.value;
  }
}

Note: Use property-based injection judiciously as it makes your dependencies less transparent.

Injecting Request Context

NestJS enables you to inject the current request context into your service, which is useful for scoped providers like request-scoped services.

import { Injectable, Scope, Inject } from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
import { Request } from 'express';

@Injectable({ scope: Scope.REQUEST })
export class RequestContextService {
  constructor(@Inject(REQUEST) private request: Request) {}

  getRequestDetails() {
    return {
      method: this.request.method,
      url: this.request.url,
    };
  }
}

Using the @Inject() Decorator

The @Inject() decorator is most often used for custom injection tokens or for services not decorated with @Injectable(), such as third-party classes:

import { Injectable, Inject } from '@nestjs/common';
import { ThirdPartyService } from 'some-third-party-library';

@Injectable()
export class MyExtendedService {
  constructor(@Inject(ThirdPartyService) private thirdPartyService: ThirdPartyService) { }

  callThirdPartyFunction(): string {
    return this.thirdPartyService.performAction();
  }
}

Advanced Techniques

Understanding DI in NestJS is crucial for leveraging its full potential, especially when designing large scale applications. Integrating third-party services, creating hierarchical injectors, and using scope-based injection are parts of its advanced usage. The above examples set the basis for exploiting powerful and flexible DI in NestJS.

Conclusion

Utilizing Dependency Injection in NestJS allows developers to write modular, testable code with ease. From basic service injection to complex use cases involving custom providers, scope, and third-party libraries, mastering DI is essential for any NestJS developer. The examples provided should serve as a strong foundation for your NestJS DI adventures.