Middleware, Datenbank und Testing im Alltag
Nest.js für .NET-Entwickler, Teil 4
In den ersten drei Teilen dieser Serie haben wir die Grundlagen gelegt: Warum Nest.js für .NET-Entwickler interessant ist, wie Module, Controller und Services zusammenspielen, und wie Dependency Injection unter der Haube funktioniert. Jetzt wird es konkret. In diesem Abschlussartikel geht es um die Bausteine, die eine Nest.js-Anwendung produktionsreif machen: Guards und Interceptors für die Request-Pipeline, TypeORM für den Datenbankzugriff, das ConfigModule für Umgebungskonfiguration und Jest für effizientes Testen.
Guards und Interceptors: Die Request-Pipeline gestalten
In ASP.NET Core steuert die Middleware-Pipeline, was vor und nach einem Request passiert: Authentifizierung, Logging, Exception-Handling. Nest.js kennt ein vergleichbares Modell, differenziert jedoch feiner. Statt einer einzigen Middleware-Kette gibt es fünf spezialisierte Konzepte: Middleware, Guards, Interceptors, Pipes und Exception Filters. Jedes hat eine klar definierte Aufgabe im Request-Lebenszyklus.
Guards entscheiden, ob ein Request überhaupt zum Controller durchgelassen wird, das Pendant zu [Authorize] in ASP.NET Core, aber flexibler. Ein Guard implementiert das CanActivate-Interface und erhält den ExecutionContext, über den er auf Request-Daten und Metadaten zugreifen kann. Ein typischer Anwendungsfall ist die rollenbasierte Zugriffskontrolle:
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const roles = this.reflector
.get<string[]>(‘roles’, context.getHandler());
const user = context.switchToHttp()
.getRequest().user;
return roles.some(r => user.roles?.includes(r));
}
}
Der Reflector liest Custom-Metadaten aus, die per Decorator am Controller oder an einzelnen Methoden hinterlegt werden, ähnlich wie Custom Attributes in .NET. Der Guard wird per @UseGuards()-Decorator oder global registriert. Interceptors wiederum umschließen den gesamten Handler-Aufruf und eignen sich für Logging, Caching oder Response-Transformation. Sie arbeiten mit RxJS-Observables, was zunächst ungewöhnlich klingt, in der Praxis aber eine elegante Komposition ermöglicht: Mehrere Interceptors lassen sich wie Middleware-Schichten stapeln, ohne dass sie voneinander wissen müssen.
Für .NET-Entwickler ist die Reihenfolge der Pipeline wichtig: Middleware läuft zuerst, dann Guards, dann Interceptors (Pre-Controller), dann Pipes für Validierung und Transformation, dann der Controller selbst, dann Interceptors (Post-Controller), und schließlich Exception Filters. In ASP.NET Core ist diese Reihenfolge über die Middleware-Registrierung steuerbar; in Nest.js ist sie fest definiert. Das mag weniger flexibel klingen, sorgt aber für ein vorhersagbares Verhalten in jedem Projekt.
TypeORM: Der Datenbankzugriff
Wer Entity Framework Core kennt, wird sich bei TypeORM sofort zurechtfinden. TypeORM ist ein ORM für TypeScript und JavaScript, das sowohl das Active-Record- als auch das Repository-Pattern unterstützt. Nest.js integriert TypeORM über das @nestjs/typeorm-Paket direkt in das Modulsystem. Eine Entity sieht dabei vertraut aus:
@Entity()
export class Article {
@PrimaryGeneratedColumn()
id: number;
@Column()
title: string;
@ManyToOne(() => Author, a => a.articles)
author: Author;
}
Statt [Key] und [Required] wie in EF Core verwendet TypeORM Decorators wie @PrimaryGeneratedColumn(), @Column() und @ManyToOne(). Die Relationen werden über Callback-Funktionen definiert, was TypeScript-Typ-Inferenz ermöglicht. Die Registrierung im Modul erfolgt über TypeOrmModule.forRoot() für die Verbindung und TypeOrmModule.forFeature([Article]) für die Entities pro Modul, vergleichbar mit AddDbContext und der Konfiguration in Program.cs.
Ein wichtiger Unterschied zu EF Core: TypeORM bietet zwar Migrationen, aber die Developer Experience ist weniger ausgereift als bei EF Core. Wer komfortablere Migrationen und ein schlankeres Query-API bevorzugt, sollte sich Prisma als Alternative ansehen, einen moderneren ORM für TypeScript, der mit einem deklarativen Schema und automatischer Typ-Generierung arbeitet und sich über @nestjs/prisma ebenfalls nahtlos in Nest.js integriert.
Konfiguration: Das ConfigModule
In ASP.NET Core gehört das Configuration-System zu den Stärken des Frameworks: appsettings.json, Environment Variables, User Secrets und IOptions<T> für typisierte Konfiguration. Nest.js bietet mit dem @nestjs/config-Paket ein ähnliches System. Das ConfigModule liest .env-Dateien und Umgebungsvariablen ein und stellt sie per ConfigService bereit:
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
validationSchema: Joi.object({
DB_HOST: Joi.string().required(),
PORT: Joi.number().default(3000),
}),
}),
],
})
Das isGlobal-Flag macht den ConfigService in allen Modulen verfügbar, ohne ihn überall importieren zu müssen. Die Validierung mit Joi stellt sicher, dass fehlende oder falsche Umgebungsvariablen bereits beim Start der Anwendung auffallen, nicht erst zur Laufzeit. Das ist ein Muster, das in .NET mit Data Annotations auf Options-Klassen oder dem neuen ValidateOnStart()-Feature in .NET 8 vergleichbar ist. In Nest.js ist die Validierung direkt in das ConfigModule integriert, was die Konfiguration zu einem First-Class Citizen im Framework macht.
Testen mit Jest: Pragmatisch und integriert
Im dritten Teil dieser Serie haben wir das Testing-Modul aus DI-Perspektive betrachtet. In der Praxis geht das Testen in Nest.js aber weiter. Jest ist als Test-Runner vorkonfiguriert, anders als in .NET, wo man xUnit, NUnit oder MSTest einrichten muss. Jedes mit der CLI generierte Modul bringt bereits eine .spec.ts-Datei mit, die einen grundlegenden Test enthält. Die Einstiegshürde liegt damit praktisch bei null.
Für End-to-End-Tests bietet Nest.js den NestApplication-Kontext in Kombination mit supertest. Damit lässt sich die gesamte Anwendung im Test starten und über echte HTTP-Requests ansprechen, vergleichbar mit dem WebApplicationFactory-Ansatz in ASP.NET Core. Der Vorteil: Man testet nicht nur die Geschäftslogik, sondern auch Guards, Pipes und Interceptors im Zusammenspiel. In Kombination mit dem Testing-Modul entsteht eine Teststrategie, die von Unit-Tests über Integrationstests bis hin zu E2E-Tests alle Ebenen abdeckt, ohne dass man verschiedene Frameworks zusammenstecken muss.
Fazit: Der Blick über den Tellerrand lohnt sich
Mit diesem Artikel endet unsere Nest.js-Serie für .NET-Entwickler. Über vier Folgen haben wir gesehen, dass Nest.js kein Fremdland ist: Module statt Assemblies, Decorators statt Attributes, TypeORM statt Entity Framework – die Konzepte sind vertraut, die Syntax ist anders. Was Nest.js auszeichnet, ist die konsequente Strukturierung eines Ökosystems, das sonst für seine Fragmentierung bekannt ist. Wo Node.js-Projekte oft aus lose zusammengestückten Libraries bestehen, liefert Nest.js ein opinionated Framework mit klaren Konventionen.
Der strategische Wert liegt in der gemeinsamen Sprache: TypeScript auf dem Server bedeutet, dass Frontend- und Backend-Teams dieselben Typen, Interfaces und die Validierungslogik teilen können. In Projekten, deren Frontend bereits auf Angular, React oder Vue basiert, entfällt der Kontextwechsel zwischen zwei Sprachwelten. Das reduziert nicht nur die Einarbeitungszeit, sondern ermöglicht auch neue Teamstrukturen, in denen Entwickler flexibler zwischen Frontend und Backend wechseln können.
Nest.js ist kein Ersatz für ASP.NET Core, und das muss es auch nicht sein. Es ist eine Ergänzung im Werkzeugkasten, die genau dort ihre Stärken ausspielt, wo TypeScript bereits im Einsatz ist. Wer die architektonischen Muster aus .NET kennt und schätzt, findet in Nest.js ein Framework, das dieselben Werte vertritt: Struktur, Testbarkeit und klare Verantwortlichkeiten. Der Blick über den Tellerrand lohnt sich – nicht um die Seite zu wechseln, sondern um das eigene Repertoire zu erweitern.