Diagnosticar el error de NestJS: no se pueden resolver las dependencias, causa por causa
Aprenda a interpretar los errores de resolución de dependencias en NestJS y a solucionar sus cinco causas más comunes: proveedores faltantes, módulos no exportados, ciclos, tokens incorrectos y módulos de prueba sin estructura.
Tarde o temprano, todo proyecto NestJS se detiene al iniciar con un mensaje que indica que el contenedor no puede resolver un argumento del constructor. La redacción parece oscura, pero es precisa, y casi siempre se debe a uno de cinco errores en la configuración. Esta guía muestra cómo leer el mensaje y explica cada causa en el orden en que se deben verificar.
Leer el mensaje de error
Un fallo típico se ve así:
Nest can't resolve dependencies of the UsersService (?). Please make sure that the argument at index [0] is available in the UsersModule context.
La clase mencionada en el mensaje (UsersService) es la que Nest intentaba instanciar. Dentro de los paréntesis se enumeran cada parámetro del constructor, y el ? marca aquel que falló; index [0] indica esa posición. La parte final indica el módulo cuyo ámbito se buscó. Cada solución presentada es una forma de hacer visible ese argumento.
Causa 1: la clase nunca se registró como proveedor
El caso más simple: el archivo del servicio existe, pero ningún módulo lo enumera. Nest solo gestiona las clases que aparecen en el array providers de un módulo.
@Module({
controllers: [UsersController],
providers: [UsersService], // <-- missing? that's your error
})
export class UsersModule {}
La orden de la CLI nest generate service actualiza el módulo por usted. Los servicios escritos a mano son donde se olvida este paso.
Causa 2: el proveedor está en otro módulo que no se importa ni se exporta
Esta es la variante más frecuente entre módulos. AuthService está registrado en AuthModule, y UsersService lo inyecta, pero UsersModule no tiene ninguna importación que apunte a AuthModule:
@Module({
imports: [AuthModule], // <-- without this, AuthService is invisible here
providers: [UsersService],
})
export class UsersModule {}
Importar es solo la mitad del proceso. El módulo que posee el proveedor también debe enumerarlo en exports:
@Module({
providers: [AuthService],
exports: [AuthService], // <-- other modules can only use what you export
})
export class AuthModule {}
Un modelo mental útil: un proveedor es privado de su módulo a menos que sea exportado, y un proveedor exportado sigue siendo invisible para los módulos que no importan a su propietario. Ambas condiciones deben cumplirse.
Causa 3: dos servicios dependen el uno del otro
Si UsersService necesita OrdersService y OrdersService necesita UsersService, ninguno de los dos puede construirse primero. Nest ofrece forwardRef() para posponer la resolución. Se aplica a nivel de módulo en el momento de la importación:
// users.module.ts
@Module({
imports: [forwardRef(() => OrdersModule)],
providers: [UsersService],
exports: [UsersService],
})
export class UsersModule {}
y nuevamente en el punto de inyección, dentro del constructor del servicio:
// users.service.ts
constructor(
@Inject(forwardRef(() => OrdersService))
private ordersService: OrdersService,
) {}
Se necesita la imagen reflejada en el otro lado (OrdersModule importando forwardRef(() => UsersModule)). Considérelo como un parche temporal y no como una solución definitiva. Un ciclo generalmente indica que el comportamiento compartido se encuentra en el lugar incorrecto; al moverlo a un tercer módulo que ambos puedan importar, se elimina el ciclo y la necesidad de forwardRef por completo.
Una trampa relacionada: las importaciones circulares de archivos, a menudo a través de archivos index.ts genéricos, pueden hacer que la referencia a una clase sea undefined en el momento de la decoración. Nest entonces reporta una dependencia no resoluble, aunque los módulos parezcan correctos.
Causa 4: el token de inyección no coincide
Los proveedores personalizados se registran bajo un token, y la inyección debe utilizar exactamente ese token. Considere un proveedor de valores indexado por una cadena:
{
provide: 'CONFIG_OPTIONS',
useValue: configOptions,
}
Declarar el parámetro del constructor solo con un tipo no permitirá encontrarlo, ya que el tipo en sí no es un token. Utilice @Inject() con la misma cadena:
constructor(@Inject('CONFIG_OPTIONS') private config: ConfigOptions) {}
Los tipos en TypeScript también desaparecen en tiempo de ejecución, por lo que una interfaz nunca puede servir como token por sí sola.
Los repositorios de TypeORM fallan de la misma manera. Especificar el parámetro como una clase de repositorio no coincide con el token registrado por Nest:
// Wrong
constructor(private repo: UserRepository) {}
La forma correcta utiliza @InjectRepository() con la entidad:
// Right
constructor(
@InjectRepository(User)
private repo: Repository<User>,
) {}
Para que ese token exista en primer lugar, el módulo también debe importar TypeOrmModule.forFeature([User]).
Causa 5: el módulo de pruebas carece de proveedores o mocks
A veces la aplicación arranca sin problemas mientras que las pruebas unitarias generan el mismo error. Test.createTestingModule crea un contenedor completamente nuevo que contiene únicamente lo que se declara, por lo que todas las dependencias de la clase a probar deben proporcionarse, generalmente como un mock vinculado al token correcto:
const module = await Test.createTestingModule({
providers: [
UsersService,
{
provide: getRepositoryToken(User),
useValue: mockRepository, // <-- every dependency needs one of these
},
],
}).compile();
getRepositoryToken(User) genera el mismo token que busca @InjectRepository(User). Si el error solo aparece en las pruebas, la configuración en entorno de producción está bien y la configuración de las pruebas es incompleta.
Lista de verificación para depurar
Sigua estos pasos en orden:
- ¿Está la clase incluida en los
providersdel módulo correspondiente? - ¿Se ha importado el módulo que contiene el proveedor donde se utiliza, y ¿lo exporta?
forwardRef y luego refactoree.Puntos clave
- El
?y el índice en el mensaje identifican con precisión qué argumento del constructor falta y en qué ámbito de módulo. - La visibilidad en Nest es explícita: registrar, exportar, importar.
forwardRefoculta los ciclos; extraer un módulo compartido los soluciona.- Son los tokens, y no los tipos de TypeScript, los que controlan la inyección en tiempo de ejecución.
- Los módulos de pruebas son contenedores separados y necesitan su propia configuración completa.
Para obtener más información sobre cómo mantener saludables los límites de los módulos a medida que crece la base de código, consulte seis reglas de DDD para estructurar dominios en aplicaciones NestJS.