Die Diagnose des Fehlers „NestJS kann Abhängigkeiten nicht auflösen“ – Ursache für Ursache
Erlernen Sie, die Fehlermeldungen zur Abhängigkeitslösung in NestJS zu deuten, und beheben Sie ihre fünf häufigsten Ursachen: fehlende Provider, nicht exportierte Module, Zyklen, falsche Tokens sowie Testmodule ohne Kapselung.
Erlaubt sich Zeit, kommt jedes NestJS-Projekt beim Start nicht weiter und zeigt eine Meldung an, dass der Container einen Konstruktorparameter nicht auflösen kann. Der Wortlaut erscheint unklar, ist aber präzise und lässt sich fast immer auf einen von fünf Verkabelungsfehlern zurückführen. Diese Anleitung zeigt, wie man die Meldung interpretiert, und erläutert jede Ursache in der Reihenfolge, in der man sie überprüfen sollte.
Lesen der Fehlermeldung
Ein typischer Fehler sieht so aus:
Nest can't resolve dependencies of the UsersService (?). Please make sure that the argument at index [0] is available in the UsersModule context.
Die in der Meldung genannte Klasse (UsersService) ist die, die Nest instanzieren wollte. In den Klammern sind alle Konstruktorparameter aufgelistet, wobei das ? den fehlerhaften Parameter markiert; index [0] gibt diese Position an. Der letzte Teil nennt das Modul, dessen Scope durchsucht wurde. Jede der unten aufgeführten Lösungen dient dazu, diesen Parameter sichtbar zu machen.
Ursache 1: Die Klasse wurde nie als Anbieter registriert
Der einfachste Fall: Die Service-Datei existiert, aber kein Modul listet sie auf. Nest verwaltet nur Klassen, die im providers-Array eines Moduls vorhanden sind.
@Module({
controllers: [UsersController],
providers: [UsersService], // <-- missing? that's your error
})
export class UsersModule {}
Der CLI-Befehl nest generate service aktualisiert das Modul für Sie. Bei manuell geschriebenen Services wird dieser Schritt oft übersehen.
Ursache 2: Der Provider befindet sich in einem anderen Modul, das weder importiert noch exportiert wird
Das ist die häufigste Variante zwischen Modulen. AuthService wird in AuthModule registriert und von UsersService injiziert, doch UsersModule enthält keinen Import auf AuthModule:
@Module({
imports: [AuthModule], // <-- without this, AuthService is invisible here
providers: [UsersService],
})
export class UsersModule {}
Importieren ist nur die Hälfte des Vertrags. Das Modul, das den Provider besitzt, muss ihn auch unter exports auflisten:
@Module({
providers: [AuthService],
exports: [AuthService], // <-- other modules can only use what you export
})
export class AuthModule {}
Ein nützliches mentales Modell: Ein Provider ist innerhalb seines Moduls privat, es sei denn, er wird exportiert. Ein exportierter Provider bleibt den Modulen, die seinen Eigentümer nicht importieren, weiterhin unsichtbar. Beide Bedingungen müssen erfüllt sein.
Ursache 3: Zwei Dienste sind voneinander abhängig
Falls UsersService auf OrdersService angewiesen ist und OrdersService wiederum auf UsersService, kann keiner der Dienste zuerst erstellt werden. Nest bietet forwardRef(), um die Auflösung zu verschieben. Dieser wird auf Modulebene für den Import angewendet:
// users.module.ts
@Module({
imports: [forwardRef(() => OrdersModule)],
providers: [UsersService],
exports: [UsersService],
})
export class UsersModule {}
und erneut am Einsetzpunkt in dem Konstruktor des Dienstes:
// users.service.ts
constructor(
@Inject(forwardRef(() => OrdersService))
private ordersService: OrdersService,
) {}
Das Spiegelbild ist auf der anderen Seite erforderlich (OrdersModule importiert forwardRef(() => UsersModule)). Betrachten Sie dies eher als vorübergehenden Patch denn als Lösung. Ein Zyklus bedeutet in der Regel, dass gemeinsames Verhalten an der falschen Stelle liegt; indem es in ein drittes Modul verlegt wird, das beide importieren können, entfällt der Zyklus sowie die Notwendigkeit von forwardRef gänzlich.
Eine damit verbundene Falle: Zirkuläre Dateiimporte, oft über sogenannte index.ts-Dateien, können dazu führen, dass eine Klassenzuweisung zum Zeitpunkt der Dekoration undefined ist. Nest meldet dann eine unlösbare Abhängigkeit, obwohl die Module korrekt erscheinen.
Ursache 4: Der Injektionstoken stimmt nicht überein
Kundenspezifische Anbieter werden unter einem Token registriert, und die Injektion muss genau dieses Token verwenden. Betrachten Sie einen Wertanbieter, der nach einem String keygt:
{
provide: 'CONFIG_OPTIONS',
useValue: configOptions,
}
Wenn der Konstruktorparameter nur mit einem Typ deklariert wird, kann er nicht gefunden werden, da der Typ kein Token ist. Verwenden Sie @Inject() zusammen mit dem gleichen String:
constructor(@Inject('CONFIG_OPTIONS') private config: ConfigOptions) {}
Auch Typen in TypeScript verschwinden zur Laufzeit, weshalb eine Schnittstelle niemals allein als Token dienen kann.
TypeORM-Repositories versagen auf dieselbe Weise. Wenn der Parameter als Repository-Klasse definiert wird, stimmt dies nicht mit dem von Nest registrierten Token überein:
// Wrong
constructor(private repo: UserRepository) {}
Die funktionierende Variante verwendet @InjectRepository() in Kombination mit der Entität:
// Right
constructor(
@InjectRepository(User)
private repo: Repository<User>,
) {}
Dass dieses Token überhaupt existieren kann, setzt voraus, dass das Modul außerdem TypeOrmModule.forFeature([User]) importiert.
Ursache 5: Das Testmodul fehlen Provider oder Mocks
Mannchmal startet die Anwendung einwandfrei, während die Unit-Tests denselben Fehler auslösen. Test.createTestingModule erstellt einen völlig neuen Container, der nur das enthält, was man deklariert, sodass jede Abhängigkeit der getesteten Klasse bereitgestellt werden muss – in der Regel als Mock, der mit dem richtigen Token verknüpft ist:
const module = await Test.createTestingModule({
providers: [
UsersService,
{
provide: getRepositoryToken(User),
useValue: mockRepository, // <-- every dependency needs one of these
},
],
}).compile();
getRepositoryToken(User) erzeugt dasselbe Token, nach dem @InjectRepository(User) sucht. Wenn der Fehler nur in Tests auftritt, ist die Produktivkonfiguration in Ordnung und die Testeinrichtung unvollständig.
Checkliste zur Fehlersuche
Gehen Sie diese Punkte in dieser Reihenfolge durch:
- Steht die Klasse in den
providersdes entsprechenden Moduls? - Wird das zugehörige Modul dort importiert, wo der Provider verwendet wird, und exportiert es den Provider?
forwardRef, bevor Sie refaktorieren.Haupterkenntnisse
- Das
?sowie die Nummer in der Meldung geben genau an, welcher Konstruktorparameter fehlt und innerhalb des Scope welches Moduls. - Die Sichtbarkeit in Nest ist explizit: registrieren, exportieren, importieren.
forwardRefversteckt Zyklen; das Extrahieren eines gemeinsamen Moduls behebt sie.- Tokens, nicht TypeScript-Typen, steuern die Einbettung zur Laufzeit.
- Testmodule sind separate Container und benötigen ihre eigene vollständige Verkabelung.
Für weitere Informationen zur Aufrechterhaltung gesunder Modulgrenzen, wenn sich die Codebasis vergrößert, siehe sechs DDD-Regeln zur Strukturierung von Domänen in NestJS-Anwendungen.