Nestjs Dependency Injection and DDD / Clean Architecture

It is not possible to resolve dependency by the interface in NestJS due to the language limitations/features (see structural vs nominal typing).

And, if you are using an interface to define a (type of) dependency, then you have to use string tokens. But, you also can use class itself, or its name as a string literal, so you don't need to mention it during injection in, say, dependant's constructor.

Example:

// *** app.module.ts ***
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { AppServiceMock } from './app.service.mock';

process.env.NODE_ENV = 'test'; // or 'development'

const appServiceProvider = {
  provide: AppService, // or string token 'AppService'
  useClass: process.env.NODE_ENV === 'test' ? AppServiceMock : AppService,
};

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

// *** app.controller.ts ***
import { Get, Controller } from '@nestjs/common';
import { AppService } from './app.service';

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

  @Get()
  root(): string {
    return this.appService.root();
  }
}

You also can use an abstract class instead of an interface or give both interface and implementation class a similar name (and use aliases in-place).

Yes, comparing to C#/Java this might look like a dirty hack. Just keep in mind that interfaces are design-time only. In my example, AppServiceMock and AppService are not even inheriting from interface nor abstract/base class (in real world, they should, of course) and everything will work as long as they implement method root(): string.

Quote from the NestJS docs on this topic:

NOTICE

Instead of a custom token, we have used the ConfigService class, and therefore we have overridden the default implementation.


Export a symbol or a string along with your interface with the same name

export interface IService {
  get(): Promise<string>  
}

export const IService = Symbol("IService");

Now you can basically use IService as both the interface and the dependency token

import { IService } from '../interfaces/service';

@Injectable()
export class ServiceImplementation implements IService { // Used as an interface
  get(): Promise<string> {
    return Promise.resolve(`Hello World`);
  }
}
import { IService } from './interfaces/service';
import { ServiceImplementation} from './impl/service';
...

@Module({
  imports: [],
  controllers: [AppController],
  providers: [{
    provide: IService, // Used as a symbol
    useClass: ServiceImplementation
  }],
})
export class AppModule {}
import { IService } from '../interfaces/service';

@Controller()
export class AppController {
  // Used both as interface and symbol
  constructor(@Inject(IService) private readonly service: IService) {}

  @Get()
  index(): Promise<string> {
    return this.service.get(); // returns Hello World
  }
}