NestJS: Feste Struktur für Node.js-Anwendungen mit TypeScript

Wie das Framework mit Modulen, Dependency Injection und Dekoratoren Ordnung in Node.js-Backends bringt – und wann ein schlankeres Werkzeug genügt
Aktualisiert:
Abstract
- #Nest.js
- #Node.js
- #Server-Side Development
- #TypeScript
- #Technologien
- #Dependency Injection
- #REST-API
- #Microservices
Warum größere Node.js-Projekte von Konventionen profitieren und was NestJS dafür von Angular übernommen hat
Ein Node.js-Backend beginnt oft mit wenigen Zeilen Express-Code: ein Server, drei Routen, eine Datenbankverbindung. Zwei Jahre später arbeiten fünf Entwicklerinnen und Entwickler daran, es gibt 80 Routen, und jede Person hat ihre eigene Vorstellung davon, wo Validierung, Rechteprüfung und Datenbankzugriffe hingehören. Neue Teammitglieder brauchen Wochen, um sich zurechtzufinden.
Für dieses Problem ist NestJS gebaut. Das Framework, das der Entwickler Kamil Myśliwiec 2017 veröffentlichte, macht Node.js nicht schneller. Es gibt Backends eine feste Architektur vor, die sich in jedem Projekt wiederholt. Das ist sein eigentlicher Nutzen – und zugleich der Grund, warum es für manche Vorhaben zu schwer ist.
Was NestJS ist und was es nicht ist
NestJS ist ein Framework für serverseitige Anwendungen auf Basis von Node.js, also der Laufzeitumgebung, die JavaScript außerhalb des Browsers ausführt. Es ist durchgängig in TypeScript geschrieben, der typisierten Erweiterung von JavaScript, und ist auch für eigene Projekte darauf ausgelegt; reines JavaScript ist mit Babel ebenfalls möglich.
Wichtig für die Einordnung: NestJS ersetzt Express oder Fastify nicht, sondern baut auf ihnen auf. Diese beiden bekannten HTTP-Frameworks kümmern sich um die eigentliche Verarbeitung von Anfragen. NestJS liegt als Schicht darüber und regelt, wie der Code organisiert wird. Standardmäßig arbeitet es mit Express; über einen Adapter lässt sich Fastify einsetzen, das laut eigener Messungen der Fastify-Entwickler mehr Anfragen pro Sekunde verarbeitet. Seit Version 12.3 empfiehlt das NestJS-Team zusätzlich einen eigenen Adapter für Node.js (Paket @nestjs/platform-node). Nach dessen Messungen bewältigt er rund 50 Prozent mehr Anfragen pro Sekunde als Express und etwa 5 Prozent mehr als Fastify; mit Version 13 soll er Express als Standard ablösen. Für die meisten Geschäftsanwendungen sind diese Unterschiede allerdings zweitrangig, weil Datenbank und externe Dienste die Antwortzeiten bestimmen.
Gelegentlich liest man, NestJS folge dem MVC-Muster von Ruby on Rails oder Laravel. Das trifft nur am Rand zu. Das tatsächliche Vorbild ist Angular, das Frontend-Framework von Google: Module, Dependency Injection und Dekoratoren funktionieren in NestJS sehr ähnlich. Wer Angular kennt, findet sich schnell zurecht.
Die Bausteine: Module, Controller und Provider
Eine NestJS-Anwendung besteht aus drei Grundbausteinen, die an einem Beispiel am leichtesten zu verstehen sind. Angenommen, ein mittelständischer Großhändler baut eine interne API für seine Kundendaten.
Controller nehmen HTTP-Anfragen entgegen und geben Antworten zurück. Sie entscheiden, welche Route zu welcher Methode gehört, enthalten aber selbst möglichst keine Geschäftslogik. Gekennzeichnet werden sie mit Dekoratoren – das sind die Anmerkungen mit @, die einer Klasse oder Methode zusätzliche Bedeutung geben.
// kunden.controller.ts
import {
Body,
Controller,
Get,
Param,
ParseIntPipe,
Post,
} from '@nestjs/common';
import { KundenService } from './kunden.service.js';
import { KundeAnlegenDto } from './dto/kunde-anlegen.dto.js';
@Controller('kunden')
export class KundenController {
constructor(private readonly kundenService: KundenService) {}
@Get()
alle() {
return this.kundenService.alle();
}
@Get(':id')
eins(@Param('id', ParseIntPipe) id: number) {
return this.kundenService.eins(id);
}
@Post()
anlegen(@Body() dto: KundeAnlegenDto) {
return this.kundenService.anlegen(dto);
}
}
Provider, meist Services genannt, enthalten die eigentliche Logik: Daten laden, Regeln prüfen, andere Systeme ansprechen. Der Controller oben erzeugt seinen Service nicht selbst, sondern bekommt ihn im Konstruktor übergeben. Das ist Dependency Injection: Das Framework baut die benötigten Objekte und reicht sie weiter. Der praktische Vorteil zeigt sich beim Testen, weil sich der echte Service durch eine Attrappe ersetzen lässt, die keine Datenbank braucht.
// kunden.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { KundeAnlegenDto } from './dto/kunde-anlegen.dto.js';
export interface Kunde {
id: number;
name: string;
email: string;
}
@Injectable()
export class KundenService {
// Im echten Projekt stünde hier ein Datenbankzugriff, z. B. über Prisma oder TypeORM
private readonly kunden: Kunde[] = [];
alle(): Kunde[] {
return this.kunden;
}
eins(id: number): Kunde {
const kunde = this.kunden.find((k) => k.id === id);
if (!kunde) {
throw new NotFoundException(`Kunde ${id} nicht gefunden`);
}
return kunde;
}
anlegen(dto: KundeAnlegenDto): Kunde {
const kunde = { id: this.kunden.length + 1, ...dto };
this.kunden.push(kunde);
return kunde;
}
}
Module fassen zusammengehörige Controller und Provider zusammen, etwa alles rund um Kunden, Bestellungen oder Rechnungen. Ein Modul legt fest, was es nach außen anbietet und was es von anderen Modulen braucht. So entstehen klare Grenzen innerhalb einer Anwendung.
// kunden.module.ts
import { Module } from '@nestjs/common';
import { KundenController } from './kunden.controller.js';
import { KundenService } from './kunden.service.js';
@Module({
controllers: [KundenController],
providers: [KundenService],
exports: [KundenService],
})
export class KundenModule {}
Diese Grenzen sind mehr als Ordnungsliebe. Wer ein Backend später in mehrere Dienste aufteilen möchte, findet in den Modulen bereits die natürlichen Schnittlinien.
Validierung, Rechte und Fehler an einer Stelle
In vielen gewachsenen Express-Projekten steckt die Prüfung eingehender Daten verstreut in den Routen. NestJS sieht dafür eigene Bausteine vor, die sich zentral einhängen lassen.
Pipes prüfen und wandeln Eingaben, bevor sie den Controller erreichen. Im Beispiel oben sorgt ParseIntPipe dafür, dass die ID aus der Adresse als Zahl ankommt. Für komplexe Daten beschreibt man die erwartete Form in einer DTO-Klasse (Data Transfer Object) und versieht die Felder mit Regeln:
// dto/kunde-anlegen.dto.ts
import { IsEmail, IsString, MinLength } from 'class-validator';
export class KundeAnlegenDto {
@IsString()
@MinLength(2)
name!: string;
@IsEmail()
email!: string;
}
Eine globale ValidationPipe prüft dann jede Anfrage gegen diese Regeln und lehnt ungültige Daten mit einer verständlichen Fehlermeldung ab:
// main.ts
import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module.js';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// Unbekannte Felder entfernen und ungültige Anfragen ablehnen
app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true }));
await app.listen(process.env.PORT ?? 3000);
}
bootstrap();
Guards entscheiden, ob eine Anfrage überhaupt bearbeitet werden darf, etwa anhand eines Tokens oder einer Benutzerrolle. Interceptors können Antworten umformen, Laufzeiten messen oder Ergebnisse zwischenspeichern. Exception Filters wandeln Fehler in einheitliche Antworten um. Für Sicherheitsfragen ist das ein echter Gewinn: Eine Rechteprüfung, die als Guard an einer Stelle definiert ist, wird seltener vergessen als eine, die jede Route selbst erledigen muss. Sie ersetzt aber kein Sicherheitskonzept – Themen wie Ratenbegrenzung, sichere Header und Abhängigkeitspflege bleiben Aufgabe des Teams.
Mehr als REST: GraphQL, Microservices und Werkzeuge
NestJS ist nicht auf klassische REST-APIs beschränkt. Mit offiziellen Zusatzpaketen lassen sich GraphQL-Schnittstellen bauen, OpenAPI-Dokumentation automatisch aus Controllern und DTOs erzeugen oder Konfiguration aus Umgebungsvariablen laden. Wer zwischen GraphQL und REST abwägt, findet die Unterschiede in unserem Beitrag GraphQL oder REST.
Für verteilte Systeme bringt NestJS ein eigenes Microservice-Modul mit. Dienste können darüber nicht nur per HTTP, sondern auch über Nachrichtensysteme wie RabbitMQ, Kafka, NATS oder Redis sowie über gRPC kommunizieren. Der Code der Controller bleibt dabei weitgehend gleich, nur die Transportschicht wechselt. Ob ein Monolith oder Microservices sinnvoll sind, ist allerdings eine Architekturfrage, die das Framework nicht beantwortet; unser Infothek-Artikel zum Weg vom Node.js-Monolithen zur Microservice-Architektur ordnet das ein.
Der Einstieg läuft über die Kommandozeile. Das CLI-Werkzeug legt ein Projekt mit TypeScript-Konfiguration, Testumgebung und Linting an und erzeugt auf Wunsch komplette Bausteine:
npm i -g @nestjs/cli
nest new kundenportal
cd kundenportal
nest generate resource kunden
Der letzte Befehl erzeugt Modul, Controller, Service, DTOs und Testdateien für eine neue Ressource. Das spart Tipparbeit und sorgt vor allem dafür, dass jede Ressource gleich aufgebaut ist.
Wo NestJS an Grenzen stößt
Die Struktur hat ihren Preis, und der sollte vor der Entscheidung bekannt sein.
Mehr Code für wenig Funktion: Für einen einfachen Webhook-Empfänger oder eine API mit fünf Routen sind Module, DTOs und Provider Ballast. Ein schlankes Framework wie Express, Fastify oder Hono ist hier schneller geschrieben und leichter zu überblicken.
Dekoratoren und Metadaten: NestJS stützt sich auf die ältere, experimentelle Dekorator-Variante von TypeScript und auf Metadaten zur Laufzeit. Der neuere Dekorator-Vorschlag für JavaScript, der noch nicht Teil des Sprachstandards ist, funktioniert anders. Das ist im Alltag selten ein Problem, bindet ein Projekt aber an eine bestimmte Compiler-Konfiguration.
Startzeit in Serverless-Umgebungen: Beim Start baut NestJS den gesamten Abhängigkeitsgraphen auf. In Umgebungen, in denen Funktionen häufig kalt starten, verlängert das die erste Antwort. Lazy Loading von Modulen mildert das, beseitigt es aber nicht.
Lernaufwand: Begriffe wie Provider, Injection Scopes oder dynamische Module sind für Teams ohne Angular- oder Java-Hintergrund ungewohnt. Die Einarbeitung lohnt sich vor allem dann, wenn das Projekt lange lebt.
Was das für Ihr Team bedeutet
Unsere Einschätzung: NestJS spielt seine Stärken in Backends aus, an denen mehrere Personen über Jahre arbeiten – Kundenportale, interne Fachanwendungen, APIs für Web und App. Dort zahlt sich die vorgegebene Ordnung aus, weil Einarbeitung, Code-Reviews und Tests einfacher werden. Viele Teams aus der Java- oder .NET-Welt fühlen sich mit dem Konzept schnell vertraut, weil Spring oder ASP.NET Core ähnlich denken.
Überschätzt wird NestJS als Performance-Gewinn. Das Framework macht Anwendungen nicht schneller als Express oder Fastify, auf denen es aufsetzt, sondern wartbarer. Für kleine Dienste, Prototypen mit kurzer Lebensdauer oder Funktionen in Serverless-Umgebungen ist ein leichteres Werkzeug oft die bessere Wahl.
Wer ein bestehendes Express-Projekt umstellen möchte, sollte das schrittweise tun: zunächst neue Bereiche als Module bauen, dann Altbestand nach und nach überführen. Hinweise zum Betrieb der fertigen Anwendung gibt unser Beitrag zum Node.js-Deployment in Produktivumgebungen. Wenn Sie ein Backend mit TypeScript und Node.js planen oder Ihr Team in NestJS einarbeiten möchten, finden Sie auf unserer Seite zu TypeScript und Node.js für APIs und Microservices Beratung und Seminare.
Häufige Fragen zu NestJS
Setzt NestJS Angular-Kenntnisse voraus?
Nein. Die Konzepte sind ähnlich, und wer Angular kennt, lernt schneller. Voraussetzung sind aber vor allem solide Kenntnisse in TypeScript und Node.js. Die offizielle Dokumentation erklärt Module und Dependency Injection auch für Einsteiger.
Welche Datenbanken lassen sich mit NestJS nutzen?
Grundsätzlich jede, für die es einen Node.js-Treiber gibt. Verbreitet sind ORMs wie Prisma, TypeORM, MikroORM oder Drizzle sowie Mongoose für MongoDB. Für einige davon gibt es offizielle oder etablierte Integrationspakete, die die Verbindung als Provider bereitstellen.
Eignet sich NestJS für kleine Projekte?
Es funktioniert, ist aber oft mehr Aufwand als nötig. Sinnvoll ist es bei kleinen Projekten vor allem dann, wenn absehbar ist, dass sie wachsen, oder wenn ein Team ohnehin mehrere NestJS-Anwendungen betreut und überall dieselbe Struktur haben möchte.
Kann NestJS auch ohne HTTP laufen?
Ja. Neben Webservern unterstützt NestJS sogenannte Standalone-Anwendungen, etwa für Hintergrundjobs oder Kommandozeilenwerkzeuge. Dienste, die nur Nachrichten aus einer Warteschlange verarbeiten, lassen sich als Microservice ohne HTTP-Server betreiben. Die Module und Services lassen sich dabei unverändert weiterverwenden.
Kategorien
Schlagworte