How to inject the interface of a service in the constructor of a controller in Nestjs?

I have an app that receives a service as a dependency on the controller, so far so good, but I would like to find a way to instead of declaring the specific implementation of that service, to be able to "ask" from the controller for the interface that this service implements to decouple of the concrete implementation of that service. How is this done in nest js?

Upvotes: 9

Views: 9041

Answers (2)

pr0gger
pr0gger

Reputation: 21

You can achieve this decoupling with an interface + injection token string or use an abstract class.

I prefer the abstract class approach due to the fact that you can avoid those injection tokens.

logger.interface.ts

export abstract class ILogger {
}

logger.ts

@Injectable()
export class Logger implements ILogger {  
}

example.module.ts

@Module({
  providers: [
  {
    provide: ILogger,
    useClass: Logger
  
  }
  ],
  controllers: [
    ExampleController
  ]
})
export class GreetingModule {}

example.controller.ts

@Controller('example')
export class ExampleController {
 constructor(
   private readonly logger: ILogger
 ) {}
 ...
}

Upvotes: 2

Jason White
Jason White

Reputation: 5813

To do this you have to create an injection token for your interface and use the @Inject() decorator with the injection token when injecting your service. Then in your module you can declare which implementation to provide for that injection token.

Below is a simple greeting service interface and our injection token that will be used when registering our service as a provider.

greeting-service.interface.ts

// This will be our injection token.
export const GREETING_SERVICE = 'GREETING SERVICE';

export interface IGreetingService {
  greet(name: string): Promise<string>;
}

A basic service that will implements our greeting interface...

professional-greeting.service.ts

import { Injectable } from '@nestjs/common';
import { IGreetingService } from './greeting-service.interface';

@Injectable()
export class ProfessionalGreetingService implements IGreetingService {
  public async greet(name: string): Promise<string> {
    return `Hello ${name}, how are you today?`;
  }
}

And our greeting module where we register our service using the token...

greeting.module.ts

import { Module } from '@nestjs/common';
import { ProfessionalGreetingService } from './services/professional-greeting.service';
import { GREETING_SERVICE } from './services/greeting-service.interface';
import { GreetingController } from './controllers/greeting.controller';

@Module({
  providers: [
    {
      // You can switch useClass to different implementation
      useClass: ProfessionalGreetingService,
      provide: GREETING_SERVICE
    }
  ],
  controllers: [
    GreetingController
  ]
})
export class GreetingModule {}

Now when we inject our service, we can use the @Inject() decorator with our injection token. Whichever implementation you provived to useClass in our GreetingModule will be injected...

greeting.controller.ts

import { Controller, Get, Inject, Query } from '@nestjs/common';
import { GREETING_SERVICE, IGreetingService } from '../services/greeting-service.interface';

@Controller('greeting')
export class GreetingController {
  constructor(
    @Inject(GREETING_SERVICE)
    private readonly _greetingService: IGreetingService
  ) {}

  @Get()
  public async getGreeting(@Query('name') name: string): Promise<string> {
    return await this._greetingService.greet(name || 'John');
  }
}

https://jasonwhite.xyz/posts/2020/10/20/nestjs-dependency-injection-decoupling-services-with-interfaces/

https://github.com/jmw5598/nestjs-di-decoupling-with-interfaces https://docs.nestjs.com/fundamentals/custom-providers

Upvotes: 14

Related Questions