Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Lesedauer 6 Min.

Dependency Injection in Nest.js, Mehr als nur ein Container

Nest.js baut auf dem Dependency-Injection-Prinzip auf, setzt aber an einigen Stellen eigene Akzente.
© EMGenie

Dependency Injection gehört für .NET-Entwickler zum Alltag. AddScoped, AddSingleton, AddTransient, die Registrierung im DI-Container ist längst Routine. Nest.js basiert auf demselben Prinzip, weist aber einige Besonderheiten auf. In diesem Artikel sehen wir uns an, wie Nest.js Abhängigkeiten auflöst, welche Scopes es gibt, wie sich Custom Providers nutzen lassen und warum das Testing-Modul ein echtes Highlight ist.

Scopes: Singleton ist der Standard

In ASP.NET Core entscheidet man bei der Registrierung, ob ein Service Scoped, Singleton oder Transient ist. AddScoped erstellt eine Instanz pro HTTP-Request, AddSingleton eine für die gesamte Anwendungslebensdauer, AddTransient jedes Mal eine neue. In Nest.js gibt es dieselben drei Konzepte, aber mit einem entscheidenden Unterschied: Der Standard-Scope ist Singleton.

Das ist kein Zufall, sondern eine bewusste Designentscheidung. Node.js arbeitet single-threaded mit einem Event Loop, es gibt kein Thread-Pool-Modell wie in ASP.NET Core, bei dem Scoped Services an den jeweiligen Request-Thread gebunden sind. Ein Singleton in Nest.js ist deshalb nicht dasselbe Risiko wie in einer Multi-Threaded-Umgebung: Es gibt keine Race Conditions durch parallele Threads. Für zustandslose Services, und das sind die meisten, ist Singleton die performanteste und einfachste Wahl.

Braucht man dennoch einen Request-gebundenen Scope, etwa für Multi-Tenancy, bei der jeder Request seinen eigenen Datenbankkontext benötigt, lässt sich das explizit konfigurieren:

 

@Injectable({ scope: Scope.REQUEST })
 export class TenantService {
   constructor(@Inject(REQUEST) private req: Request) {}
   getTenantId(): string {
     return this.req.headers[‘x-tenant-id’];
   }
 }

 

Das Pendant zu AddScoped ist also Scope.REQUEST, und Scope.TRANSIENT entspricht AddTransient. Wichtig zu wissen: Sobald ein Service einen Request-Scope hat, propagiert Nest.js diesen Scope durch die gesamte Abhängigkeitskette nach oben. Ein Controller, der einen Request-Scoped Service injiziert, wird selbst Request-Scoped. In .NET ist das nicht anders, dort warnt der Container sogar vor Captive Dependencies, wenn ein Scoped Service in einen Singleton injiziert wird. Nest.js löst das Problem elegant, indem es die Scope-Propagation automatisch handhabt.

Custom Providers: Flexibilität jenseits der Klasse

In ASP.NET Core kennt man Factory-Registrierungen: builder.Services.AddScoped(sp => new MyService(sp.GetService<IDep>())). Nest.js bietet vier Provider-Varianten, die noch einen Schritt weiter gehen: useClass für alternative Implementierungen, useValue für statische Werte oder Konfigurationsobjekte, useFactory für dynamische Erzeugung mit Abhängigkeiten und useExisting für Aliase auf bestehende Provider.

Ein praktisches Beispiel ist ein Datenbankprovider, der je nach Umgebung eine andere Verbindung aufbaut:

 

 

const dbProvider = {
   provide: ‘DATABASE_CONNECTION’,
   useFactory: async (config: ConfigService) => {
     const host = config.get(‘DB_HOST’);
     return createConnection({ host });
   },
   inject: [ConfigService],
 };

 

Das inject-Array gibt an, welche Abhängigkeiten die Factory erhält, ähnlich wie der ServiceProvider-Parameter in der .NET-Factory. Der Vorteil: Die Factory kann asynchron sein, was besonders bei Datenbankverbindungen oder externen Service-Initialisierungen nützlich ist. In .NET müsste man dafür auf Lazy<T> oder eine eigene Initialisierungslogik zurückgreifen.

Besonders useValue verdient Beachtung. Damit lassen sich Konfigurationsobjekte, Feature-Flags oder externe API-Clients als Provider registrieren – Werte, die keine eigene Klasse brauchen, aber trotzdem per DI verfügbar sein sollen. Das Pendant in .NET wäre IOptions<T> oder eine manuelle Registrierung mit builder.Services.AddSingleton(myConfig).

Injection Tokens: Wenn die Klasse nicht reicht

In der .NET-Welt registriert man Services gegen Interfaces: builder.Services.AddScoped<IUserService, UserService>(). Das Interface ist das Token, gegen das aufgelöst wird. In Nest.js ist die Klasse selbst das Token; das funktioniert, solange man genau eine Implementierung hat. Aber was, wenn man mehrere Logger-Implementierungen benötigt, etwa einen Logger für die Konsole und einen für ein externes System?

Hier kommen String-Tokens und der @Inject()-Decorator ins Spiel. Anstelle einer Klasse dient ein String als Schlüssel für die Auflösung:

 

 

// Registrierung im Modul
 providers: [
   { provide: 'CONSOLE_LOGGER', useClass: ConsoleLogger },
   { provide: 'EXTERNAL_LOGGER', useClass: ElkLogger },
 ]
 // Injection im Service
 constructor(
   @Inject('EXTERNAL_LOGGER') private logger: LoggerService,
 ) {}

 

Das Konzept ist vergleichbar mit Keyed Services, die Microsoft mit .NET 8 eingeführt hat. Dort registriert man mit builder.Services.AddKeyedScoped<ILogger, ElkLogger>("external") und löst mit [FromKeyedServices("external")] auf. Der Mechanismus ist derselbe, die Syntax unterscheidet sich. Wer zusätzliche Typsicherheit möchte, kann in Nest.js statt Strings auch Symbol-basierte Tokens oder die InjectionToken-Klasse verwenden; das verhindert versehentliche Namenskollisionen in größeren Projekten.

In der Praxis zeigt sich der Wert von Injection Tokens vor allem in modularen Architekturen. Wenn ein SharedModule einen Logger exportiert, aber das konsumierte Modul entscheiden soll, welche Implementierung es erhält, ermöglichen Tokens genau diese Entkopplung. Das Modul definiert den Token, der Consumer liefert die Implementierung – ein Muster, das in .NET mit Interface-Registrierung im Composition Root vertraut ist, in Nest.js aber über die Modul-Hierarchie gelöst wird.

Das Testing-Modul: DI als Testbarkeits-Turbo

Der eigentliche Payoff von Dependency Injection zeigt sich beim Testen. In .NET erstellt man typischerweise Mocks mit Moq oder NSubstitute und übergibt sie manuell an den Konstruktor. Das funktioniert, erfordert aber für jeden Test das manuelle Verdrahten aller Abhängigkeiten. Nest.js bietet hier einen eleganteren Weg: das Testing-Modul.

Mit Test.createTestingModule() baut man einen eigenen DI-Container für den Test auf, mit allen echten Providern des Moduls, aber der Möglichkeit, einzelne davon gezielt zu überschreiben:

 

const module = await Test.createTestingModule({
   providers: [UsersService, UsersRepository],
 })
 .overrideProvider(UsersRepository)
 .useValue({ findAll: jest.fn().mockResolvedValue([]) })
 .compile();
 const service = module.get(UsersService);

 

Das Testing-Modul baut den vollständigen DI-Graphen auf, ersetzt aber gezielt das UsersRepository durch einen Mock. Der UsersService erhält den Mock über den normalen DI-Mechanismus; er weiß nicht, dass er in einem Test läuft. Das Konzept ist vergleichbar mit WebApplicationFactory<T> in ASP.NET Core, nur granularer: Man muss nicht die gesamte Anwendung hochfahren, sondern kann einzelne Module isoliert testen.

Besonders hilfreich ist das Fluent-API mit overrideProvider(). In .NET müsste man dafür entweder den Service manuell im Konstruktor verdrahten oder mit ConfigureTestServices in der WebApplicationFactory arbeiten. In Nest.js ist das Override direkt im Test-Setup integriert: Weniger Boilerplate, mehr Fokus auf die eigentliche Testlogik.

Dasselbe Prinzip, andere Akzente

Dependency Injection in Nest.js ist kein Fremdkonzept. Es ist das gleiche Prinzip, das .NET-Entwickler seit Jahren nutzen, mit ein paar klugen Variationen. Singleton als Default statt Scoped, klassenbasierte Tokens statt Interface-Registrierung, asynchrone Factories als Standardfeature und ein Testing-Modul, das DI-basiertes Testen zum Kinderspiel macht. Wer die DI-Konzepte aus .NET verinnerlicht hat, wird sich in Nest.js nicht umgewöhnen müssen. Es gilt nur umzudenken an den Stellen, an denen Node.js andere Spielregeln vorgibt.

Im nächsten und letzten Teil dieser Serie werden wir den Schritt von der Theorie in die Praxis unternehmen: Wie sieht die Request-Pipeline mit Guards und Interceptors konkret aus? Wie bindet man eine Datenbank an? Und wie testet man eine Nest.js-Anwendung effizient im Alltag? Der Abschluss der Serie zeigt, wie das Gelernte in produktionsreifen Code übersetzt wird.

 

 

Neueste Beiträge

00:00
KI und Security: Aufrüsten auf beiden Seiten - Ein Interview mit Christian Wenz, Track Chair Software Security der DWX 2026
KI übernimmt knifflige Aufgaben - wie das Suchen von Sicherheitslücken. Die Erkenntnisse darüber können aber von den Guten und den Bösen verwendet werden.
3. Mär 2026
Module, Controller und Services: Die Architektur-Grundlagen von Nest.js - Nest.js für .NET-Entwickler, Teil 2
Ähnlich wie ASP.NET Core gliedert Nest.js Anwendungen in Module, Controller und Services. In diesem Artikel sehen wir uns an, wie die einzelnen Bausteine zusammenspielen, wo die Parallelen zu bekannten .NET-Patterns liegen und wo Nest.js eigene Wege geht.
6 Minuten
25. Feb 2026
Die ganze Welt der Softwareentwicklung
Ein riesiges Angebot an Wissen, das von Expert:innen lebendig vermittelt wird, gewürzt mit Kontakt zu Gleichdenkenden – das ist der Kern der DWX.
6 Minuten
19. Feb 2026

Das könnte Dich auch interessieren

Batterie-Management mit SignalRC - Der DDC-Truck, Teil 4
Das Batterie-Management-System (BMS) von RC-Modellen benötigt verlässliche Telemetrie.
6 Minuten
12. Feb 2026
Version 30 von List & Label mit neuen Cloud-Funktionen - Reportgenerator
Die neue Version des Reporting-Tools List & Label unterstützt Entwickler:innen mit neuen Features, die speziell für Cloud- und Webumgebungen konzipiert sind.
2 Minuten
21. Okt 2024
SignalRC WebRTC - Der DDC-Truck, Teil 3
WebRTC ist als Tool ideal geeignet, um Videodaten von RC-Modellen in Echtzeit zu übertragen.
7 Minuten
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige