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
}
}