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.

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.
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:
# Install Mongoose integration
npm install @nestjs/mongoose mongooseDie Verbindung wird im Root-Modul registriert:
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. 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.
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { HydratedDocument, Types } from 'mongoose';
// Document type for TypeScript autocomplete
export type UserDocument = HydratedDocument<User>;
@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.
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.
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<UserDocument>,
) {}
async findById(id: string): Promise<UserDocument> {
// 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<UserDocument | null> {
// Case-insensitive email lookup
return this.userModel.findOne({
email: { $regex: new RegExp(`^${email}$`, 'i') }
}).exec();
}
async create(data: Partial<User>): Promise<UserDocument> {
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.
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<OrderDocument>,
) {}
async getRevenueByMonth(year: number): Promise<MonthlyRevenue[]> {
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.
Bereit für deine Node.js / NestJS-Interviews?
Übe mit unseren interaktiven Simulatoren, Flashcards und technischen Tests.
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.
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<AccountDocument>,
) {}
async transfer(
fromId: string,
toId: string,
amount: number,
): Promise<void> {
// 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.
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.
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { HydratedDocument } from 'mongoose';
export type ProductDocument = HydratedDocument<Product>;
@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.
// 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
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.
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { HydratedDocument } from 'mongoose';
export type OrderDocument = HydratedDocument<Order>;
@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.
// Debug query performance
async analyzeQuery(category: string): Promise<void> {
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
maxPoolSizebasierend auf erwarteter Parallelität konfigurieren, Standard 100 passt für die meisten WorkloadsHydratedDocument<T>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
Fang an zu üben!
Teste dein Wissen mit unseren Interview-Simulatoren und technischen Tests.
Findest du den Bug in Node.js / NestJS?
Ein echter Codeausschnitt, ein versteckter Bug, ein Versuch pro Tag. Zum Ausprobieren ohne Konto.

Geschrieben von
Anthony Fillion-MailletGründer von SharpSkill
Seit über 10 Jahren Fullstack-Entwickler. Er leitet SharpSkill und verantwortet alles, was hier erscheint.
Aktualisiert am 28. August 2026
Teilen
Verwandte Artikel

Schnelle JavaScript ListView 2026: Windowing, Virtualisierung und Performance-Optimierung
Erfahren Sie, wie man mit Windowing und Virtualisierung performante JavaScript ListViews erstellt. React Virtualized, TanStack Virtual und weitere Techniken für 2026.

NestJS und Redis in 2026: Caching, Sessions und Technische Interviewfragen
NestJS Redis-Integration mit @nestjs/cache-manager, Session-Verwaltung mit connect-redis und häufige Interviewfragen für Node.js-Backend-Entwickler.

High Performance JavaScript ListView 2026: Virtualisierung und Optimierungstechniken
Erfahren Sie, wie Sie mit JavaScript List-Virtualisierung und React Virtualized hochperformante ListViews entwickeln. Deep-Dive in Rendering-Optimierung für große Datenmengen.