# NestJS und MongoDB 2026: Mongoose-Integration, Aggregationen und Interview-Fragen > Praxisleitfaden für NestJS 12 mit MongoDB und Mongoose 9: Schema-Design, Aggregation-Pipelines, Transaktionen und technische Interview-Fragen für Backend-Entwickler. - Published: 2026-08-28 - Updated: 2026-08-28 - Author: Anthony Fillion-Maillet - Reading time: 5 min --- NestJS 12 in Kombination mit MongoDB über Mongoose 9 bietet einen produktionsreifen Stack für skalierbare Node.js-Backends. Dieser Leitfaden behandelt Schema-Design-Patterns, Aggregation-Pipelines und die Interview-Fragen, die Senior-Kandidaten von Junioren unterscheiden. > **Kurzreferenz** > > Mongoose 9.9.4 erfordert Node.js 18+ und unterstützt MongoDB 6.0 bis 8.0. NestJS 12 liefert ESM-fähige Pakete, bleibt aber abwärtskompatibel mit CommonJS-Projekten. ## Mongoose in einer NestJS 12-Anwendung einrichten Das Paket `@nestjs/mongoose` integriert Mongoose mit der NestJS-Dependency-Injection. Die Installation erfolgt über npm: ```bash # Install Mongoose integration npm install @nestjs/mongoose mongoose ``` Die Verbindung wird im Root-Modul registriert: ```typescript // app.module.ts import { Module } from '@nestjs/common'; import { MongooseModule } from '@nestjs/mongoose'; @Module({ imports: [ MongooseModule.forRoot(process.env.MONGODB_URI, { // Connection pool size for production workloads maxPoolSize: 10, // Timeout after 10 seconds if connection fails serverSelectionTimeoutMS: 10000, }), ], }) export class AppModule {} ``` Die `forRoot`-Methode akzeptiert alle [Mongoose-Verbindungsoptionen](https://mongoosejs.com/docs/connections.html). Die Einstellung `maxPoolSize` verhindert Connection-Erschöpfung unter Last – ein häufiges Produktionsproblem. ## Schema-Design mit TypeScript-Decorators Mongoose-Schemas in NestJS verwenden Decorators aus `@nestjs/mongoose`. Jedes Schema wird einer MongoDB-Collection zugeordnet. ```typescript // user.schema.ts import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; import { HydratedDocument, Types } from 'mongoose'; // Document type for TypeScript autocomplete export type UserDocument = HydratedDocument; @Schema({ timestamps: true, // Adds createdAt and updatedAt collection: 'users', // Explicit collection name }) export class User { // MongoDB ObjectId, auto-generated _id: Types.ObjectId; @Prop({ required: true, unique: true, index: true }) email: string; @Prop({ required: true }) passwordHash: string; @Prop({ type: String, enum: ['admin', 'user', 'guest'], default: 'user' }) role: string; @Prop({ type: [String], default: [] }) permissions: string[]; } export const UserSchema = SchemaFactory.createForClass(User); ``` Der `@Prop`-Decorator definiert Feld-Constraints. Das Setzen von `index: true` auf häufig abgefragten Feldern verbessert die Leseleistung auf Kosten langsamerer Schreiboperationen. > **Interview-Einblick** > > Interviewer fragen nach dem Trade-off zwischen eingebetteten Dokumenten und Referenzen. Eingebettete Dokumente eignen sich für gemeinsam abgerufene Daten (Benutzerprofil + Einstellungen). Referenzen passen zu Daten mit unbegrenztem Wachstum oder unabhängigen Abfrageanforderungen (Benutzer + Bestellungen). ## Repository-Pattern mit Injectable Services NestJS empfiehlt die Trennung der Datenbanklogik in Services. Der `@InjectModel`-Decorator ermöglicht den Zugriff auf das Mongoose-Model. ```typescript // user.service.ts import { Injectable, NotFoundException } from '@nestjs/common'; import { InjectModel } from '@nestjs/mongoose'; import { Model, Types } from 'mongoose'; import { User, UserDocument } from './user.schema'; @Injectable() export class UserService { constructor( @InjectModel(User.name) private userModel: Model, ) {} async findById(id: string): Promise { // Validate ObjectId format before querying if (!Types.ObjectId.isValid(id)) { throw new NotFoundException('Invalid user ID format'); } const user = await this.userModel.findById(id).exec(); if (!user) { throw new NotFoundException(`User ${id} not found`); } return user; } async findByEmail(email: string): Promise { // Case-insensitive email lookup return this.userModel.findOne({ email: { $regex: new RegExp(`^${email}$`, 'i') } }).exec(); } async create(data: Partial): Promise { const user = new this.userModel(data); return user.save(); } } ``` Der Aufruf von `.exec()` gibt ein echtes Promise anstelle eines Mongoose-Query-Objekts zurück. Dies ist wichtig für korrektes async/await-Verhalten und Fehler-Stack-Traces. ## Aggregation-Pipelines für komplexe Abfragen MongoDB-Aggregationen bewältigen Reporting, Analytics und Datentransformationen, die SQL-Datenbanken mit Joins und GROUP BY lösen. ```typescript // analytics.service.ts import { Injectable } from '@nestjs/common'; import { InjectModel } from '@nestjs/mongoose'; import { Model, PipelineStage } from 'mongoose'; import { Order, OrderDocument } from './order.schema'; @Injectable() export class AnalyticsService { constructor( @InjectModel(Order.name) private orderModel: Model, ) {} async getRevenueByMonth(year: number): Promise { const pipeline: PipelineStage[] = [ // Stage 1: Filter orders by year { $match: { createdAt: { $gte: new Date(`${year}-01-01`), $lt: new Date(`${year + 1}-01-01`), }, status: 'completed', }, }, // Stage 2: Group by month, sum revenue { $group: { _id: { $month: '$createdAt' }, totalRevenue: { $sum: '$amount' }, orderCount: { $sum: 1 }, avgOrderValue: { $avg: '$amount' }, }, }, // Stage 3: Sort by month ascending { $sort: { _id: 1 } }, // Stage 4: Reshape output { $project: { _id: 0, month: '$_id', totalRevenue: { $round: ['$totalRevenue', 2] }, orderCount: 1, avgOrderValue: { $round: ['$avgOrderValue', 2] }, }, }, ]; return this.orderModel.aggregate(pipeline).exec(); } } ``` Aggregation-Pipelines verarbeiten Dokumente sequenziell durch Stufen. Jede Stufe transformiert die Ausgabe für die nächste. Die `$match`-Stufe filtert früh, um die Anzahl der in nachfolgenden Stufen verarbeiteten Dokumente zu reduzieren. ## Transaktionen für Multi-Dokument-Operationen MongoDB 4.0+ unterstützt ACID-Transaktionen über mehrere Dokumente. Transaktionen werden eingesetzt, wenn mehrere Dokumente atomar aktualisiert werden müssen. ```typescript // transfer.service.ts import { Injectable, BadRequestException } from '@nestjs/common'; import { InjectConnection, InjectModel } from '@nestjs/mongoose'; import { Connection, Model, ClientSession } from 'mongoose'; import { Account, AccountDocument } from './account.schema'; @Injectable() export class TransferService { constructor( @InjectConnection() private connection: Connection, @InjectModel(Account.name) private accountModel: Model, ) {} async transfer( fromId: string, toId: string, amount: number, ): Promise { // Start a session for the transaction const session: ClientSession = await this.connection.startSession(); try { await session.withTransaction(async () => { // Debit source account const source = await this.accountModel.findOneAndUpdate( { _id: fromId, balance: { $gte: amount } }, { $inc: { balance: -amount } }, { session, new: true }, ); if (!source) { throw new BadRequestException('Insufficient balance or account not found'); } // Credit destination account const dest = await this.accountModel.findByIdAndUpdate( toId, { $inc: { balance: amount } }, { session, new: true }, ); if (!dest) { throw new BadRequestException('Destination account not found'); } }); } finally { await session.endSession(); } } } ``` Der `session.withTransaction`-Wrapper handhabt Commit und Rollback automatisch. Wenn eine Operation einen Fehler wirft, wird die gesamte Transaktion abgebrochen. > **Produktionshinweis** > > Transaktionen erfordern ein MongoDB-Replica-Set oder Sharded-Cluster. Standalone-MongoDB-Instanzen unterstützen keine Transaktionen. Atlas M0/M2/M5-Tiers beinhalten standardmäßig Replica-Sets. ## Indexierungsstrategien für Query-Performance Indizes bestimmen die Abfrageleistung. Ohne geeignete Indizes durchsucht MongoDB ganze Collections. ```typescript // product.schema.ts import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; import { HydratedDocument } from 'mongoose'; export type ProductDocument = HydratedDocument; @Schema({ timestamps: true }) export class Product { @Prop({ required: true, index: true }) sku: string; @Prop({ required: true }) name: string; @Prop({ required: true }) category: string; @Prop({ required: true }) price: number; @Prop({ default: true }) inStock: boolean; } export const ProductSchema = SchemaFactory.createForClass(Product); // Compound index for common query pattern ProductSchema.index({ category: 1, price: -1 }); // Text index for search functionality ProductSchema.index({ name: 'text', sku: 'text' }); ``` Zusammengesetzte Indizes unterstützen Abfragen, die nach mehreren Feldern filtern oder sortieren. Der Index `{ category: 1, price: -1 }` optimiert Abfragen wie `find({ category }).sort({ price: -1 })`. ## Häufige Interview-Fragen zu NestJS und MongoDB Technische Interviews testen sowohl konzeptionelles Verständnis als auch praktische Erfahrung. Diese Fragen erscheinen häufig bei Senior-Backend-Positionen. **Frage: Wie handhabt Mongoose Connection-Pooling?** Mongoose verwaltet intern einen Connection-Pool. Die Option `maxPoolSize` (Standard: 100) begrenzt gleichzeitige Verbindungen. Jede Operation bezieht eine Verbindung aus dem Pool, führt sie aus und gibt sie zurück. Connection-Pooling vermeidet den Overhead des Aufbaus neuer TCP-Verbindungen pro Abfrage. **Frage: Wann sollten eingebettete Dokumente statt Referenzen verwendet werden?** Daten einbetten, die zusammengehören und begrenztes Wachstum haben. Lieferadressen eines Benutzers (maximal 5-10) lassen sich gut einbetten. Bestellpositionen werden in das Bestelldokument eingebettet. Referenzen eignen sich für unbegrenzte Beziehungen: Bestellungen eines Benutzers über Jahre oder Produkte in einer Kategorie. Die Regel: Wenn die Daten zu 90% gemeinsam geladen werden und unter 16MB bleiben, einbetten. **Frage: Erklären Sie die $lookup-Stufe der Aggregation-Pipeline.** Die `$lookup`-Stufe führt einen Left-Outer-Join zwischen Collections durch. Sie matched Dokumente aus einer fremden Collection basierend auf Feldgleichheit oder einer benutzerdefinierten Pipeline. Anders als SQL-Joins wird `$lookup` während der Aggregation ausgeführt und kann zusätzliche Filterung und Projektion innerhalb des Joins enthalten. ```typescript // Example: Orders with customer details const pipeline: PipelineStage[] = [ { $lookup: { from: 'customers', localField: 'customerId', foreignField: '_id', as: 'customer', }, }, { $unwind: '$customer' }, ]; ``` **Frage: Wie werden Schema-Migrationen in MongoDB gehandhabt?** MongoDB-Schemas entwickeln sich anders als SQL. Gängige Strategien: - Neue Felder mit Standardwerten hinzufügen (abwärtskompatibel) - Migrationsskripte ausführen, die bestehende Dokumente in Batches aktualisieren - Schema-Versionierung mit einem `schemaVersion`-Feld verwenden - Mongoose-Middleware (`pre('save')`) kann Dokumente beim Schreiben transformieren > **Interview-Tipp** > > Senior-Kandidaten erklären Trade-offs. Junioren listen Features auf. Bei der Frage nach eingebetteten vs. referenzierten Dokumenten sollten Abfragemuster, Dokumentgrößenlimits (16MB) und Aktualisierungshäufigkeit diskutiert werden – nicht nur "es kommt darauf an". ## Fehlerbehandlung und Validierungsmuster Mongoose-Validierung läuft vor Save-Operationen. Benutzerdefinierte Validatoren behandeln Geschäftslogik. ```typescript // order.schema.ts import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; import { HydratedDocument } from 'mongoose'; export type OrderDocument = HydratedDocument; @Schema({ timestamps: true }) export class Order { @Prop({ required: true, validate: { validator: (v: number) => v > 0, message: 'Amount must be positive', }, }) amount: number; @Prop({ type: String, enum: ['pending', 'processing', 'completed', 'cancelled'], default: 'pending', }) status: string; @Prop({ required: true }) items: OrderItem[]; } export const OrderSchema = SchemaFactory.createForClass(Order); // Pre-save hook for computed fields OrderSchema.pre('save', function (next) { // Recalculate total from items if (this.isModified('items')) { this.amount = this.items.reduce( (sum, item) => sum + item.price * item.quantity, 0, ); } next(); }); ``` Die `pre('save')`-Middleware läuft vor jeder Save-Operation. Sie wird für berechnete Felder, Audit-Logging oder kaskadierende Updates verwendet. ## Performance-Monitoring mit explain() Die `explain()`-Methode zeigt Query-Ausführungspläne. Sie wird verwendet, um fehlende Indizes und langsame Abfragen zu identifizieren. ```typescript // Debug query performance async analyzeQuery(category: string): Promise { const explanation = await this.productModel .find({ category, inStock: true }) .sort({ price: -1 }) .explain('executionStats'); console.log('Documents examined:', explanation.executionStats.totalDocsExamined); console.log('Documents returned:', explanation.executionStats.nReturned); console.log('Execution time (ms):', explanation.executionStats.executionTimeMillis); // If totalDocsExamined >> nReturned, add an index } ``` Ein Verhältnis von `totalDocsExamined` zu `nReturned` nahe 1 zeigt effiziente Index-Nutzung an. Hohe Verhältnisse signalisieren fehlende Indizes oder nicht-selektive Abfragen. ## Kernpunkte für NestJS MongoDB-Entwicklung - `maxPoolSize` basierend auf erwarteter Parallelität konfigurieren, Standard 100 passt für die meisten Workloads - `HydratedDocument` für korrektes TypeScript-Typing von Mongoose-Dokumenten verwenden - `.exec()` bei Abfragen aufrufen, um native Promises mit akkuraten Stack-Traces zu erhalten - `$match`-Stufen früh in Aggregation-Pipelines platzieren, um verarbeitete Dokumente zu reduzieren - Transaktionen erfordern Replica-Sets, Deployment-Topologie vor Abhängigkeit verifizieren - Zusammengesetzte Indizes passend zu Abfragemustern erstellen: Filterfelder zuerst, Sortierfelder danach - Dokumente mit begrenztem Wachstum einbetten, unbegrenzt wachsende Dokumente referenzieren - Query-Performance mit `explain('executionStats')` während der Entwicklung überwachen --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/de/blog/node-nestjs/nestjs-mongodb-mongoose-guide