Search code examples
typescriptnestjstypeorm

Nest can't resolve dependencies of the [ServiceName]


The error that says that the dependency can't be resolved is not clear enough. Currently, the error output says:

[ExceptionHandler] Nest can't resolve dependencies of the LeadService (LeadRepository, ?). Please make sure that the argument dependency at index [1] is available in the LeadModule context.

From this output, I can conclude that the dependency ConsentService of my LeadService. See LeadService constructor below.

Additionally, the output also puts the following suggestion:

Potential solutions:

  • If dependency is a provider, is it part of the current LeadModule?

My answer: It is a provider, but it's not part of the current module. It's a provider from the ConsentModule. See ConsentModule definition.

  • If dependency is exported from a separate @Module, is that module imported within LeadModule?
   @Module({
     imports: [ /* the Module containing dependency */ ]
   })

My answer: Yes, it is exported from the ConsentModule and it is imported in the LeadModule thus I don't understand why is this failing.

Input Code

ConsentService

@Injectable()
export class ConsentService {
  constructor(@InjectRepository(Consent) private repository: Repository<Consent>) {}
}

LeadService

@Injectable()
export class LeadService<T extends LeadPayload> {
  constructor(
    @InjectRepository(Lead)
    private leadRepository: Repository<Lead>,
    @Inject()
    private consentService: ConsentService
  ) {}
}

ConsentModule

import { Module } from '@nestjs/common';
import { ConsentService } from './consent.service';
import { Consent } from '../db/models';
import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
  imports: [TypeOrmModule.forFeature([Consent])],
  providers: [ConsentService],
  exports: [ConsentService]
})
export class ConsentModule {}

LeadModule

import { Module } from '@nestjs/common';
import { LeadService } from './lead.service';
import { Lead } from '../db/models';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ConsentModule } from './consent.module';

@Module({
  imports: [ConsentModule, TypeOrmModule.forFeature([Lead])],
  providers: [LeadService],
  exports: [LeadService]
})
export class LeadModule {}

AppModule

@Global()
@Module({
  imports: [
    ConsentModule,
    LeadModule,
    TypeOrmModule.forRoot({
      ...getDbConnectionProperties(),
      entities: [Consent, Lead]
    })
  ],
  controllers: [
    DevController,
    HealthController
  ],
  providers: []
})
export class AppModule {}

I would like to know why exactly is the error happening because I think I have declared everything correctly


Solution

  • When you use @Inject you need to provide the token (the id) that the container uses to find the object instance. This is handy when, for example you have multiple instances of a class. You can give each instance (a Custom Provider) a name/id (a string token in Nest terminology) and then @Inject("name") so that Nest will inject the correct instance of the object.

    So for your LeadService Nest doesn't seem to know what instance to inject into the consentService parameter because you've not specified a token. Because Nest can do matching using the class type as the token, you could write @Inject(ConsentService). Nest will realise it needs to instantiate an instance of ConsentService (with all its dependencies resolved) and then provide that object to the LeadService constructor.

    However often when you're using the class type as the token, it's because you've only got one instance of the class. So you can leave off the @Inject(ConsentService) entirely. Nest can match based off the types and inject an instance (a singleton instance). So for your application, just remove the @Inject() decorator from the consentService constructor parameter.

    Now, I'm going to suggest you should not use @Inject() at all. Why? Because IMO it violates the idea of Inversion of Control. The point of IoC is that the injectee (LeadService) shouldn't know what it gets injected with. All that matters is that the injected objects meet the type contract. So if you have two instances of ConsentService for example, which one should LeadService use? Well LeadService shouldn't know, but @Inject() couples LeadService to an implementation of ConsentService which limits the reusability/composability of the two classes. If one is following good SOLID principles you may want to compose objects in different combinations. IMO the correct way is to use Custom Provider metadata to specify how to wire objects together using string tokens.