# NestJS en MongoDB in 2026: Mongoose, Aggregaties en Sollicitatievragen > Praktische handleiding voor NestJS 12 met MongoDB en Mongoose 9: schema-ontwerp, aggregation pipelines, transacties en technische sollicitatievragen voor backend-ontwikkelaars. - Published: 2026-08-28 - Updated: 2026-08-28 - Author: Anthony Fillion-Maillet - Reading time: 5 min --- NestJS 12 gecombineerd met MongoDB via Mongoose 9 biedt een productie-klare stack voor het bouwen van schaalbare Node.js backends. Deze handleiding behandelt schema-ontwerppatronen, aggregation pipelines en de sollicitatievragen die senior kandidaten onderscheiden van junioren. > **Snelle Referentie** > > Mongoose 9.9.4 vereist Node.js 18+ en ondersteunt MongoDB 6.0 tot en met 8.0. NestJS 12 levert ESM-ready packages maar blijft achterwaarts compatibel met CommonJS projecten. ## Mongoose Instellen in een NestJS 12 Applicatie Het `@nestjs/mongoose` package integreert Mongoose met NestJS dependency injection. De installatie van de benodigde dependencies gebeurt via npm: ```bash # Install Mongoose integration npm install @nestjs/mongoose mongoose ``` De verbinding wordt geregistreerd in de root module: ```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 {} ``` De `forRoot` methode accepteert alle [Mongoose verbindingsopties](https://mongoosejs.com/docs/connections.html). Het instellen van `maxPoolSize` voorkomt connection-uitputting onder belasting, een veelvoorkomend productieprobleem. ## Schema-ontwerp met TypeScript Decorators Mongoose schemas in NestJS gebruiken decorators van `@nestjs/mongoose`. Elk schema wordt gekoppeld aan een MongoDB collection. ```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); ``` De `@Prop` decorator definieert veldbeperkingen. Het instellen van `index: true` op veelgebruikte velden verbetert de leesprestaties ten koste van tragere schrijfoperaties. > **Sollicitatie-inzicht** > > Interviewers vragen naar de trade-off tussen embedded documenten en referenties. Embedded documenten zijn geschikt voor data die samen wordt opgehaald (gebruikersprofiel + voorkeuren). Referenties passen bij data die onbeperkt groeit of onafhankelijke queries vereist (gebruiker + bestellingen). ## Repository Pattern met Injectable Services NestJS moedigt het scheiden van databaselogica in services aan. De `@InjectModel` decorator biedt toegang tot het 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(); } } ``` Het aanroepen van `.exec()` retourneert een echte Promise in plaats van een Mongoose Query object. Dit is belangrijk voor correct async/await gedrag en error stack traces. ## Aggregation Pipelines voor Complexe Queries MongoDB aggregaties behandelen reporting, analytics en datatransformaties die SQL databases oplossen met joins en GROUP BY. ```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 verwerken documenten sequentieel door stages. Elke stage transformeert de output voor de volgende. De `$match` stage filtert vroeg om het aantal documenten dat door volgende stages wordt verwerkt te verminderen. ## Transacties voor Multi-Document Operaties MongoDB 4.0+ ondersteunt multi-document ACID transacties. Transacties worden gebruikt wanneer meerdere documenten atomisch moeten worden bijgewerkt. ```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(); } } } ``` De `session.withTransaction` wrapper handelt commit en rollback automatisch af. Als een operatie een fout gooit, wordt de hele transactie afgebroken. > **Productie Opmerking** > > Transacties vereisen een MongoDB replica set of sharded cluster. Standalone MongoDB instanties ondersteunen geen transacties. Atlas M0/M2/M5 tiers bevatten standaard replica sets. ## Indexeringsstrategieën voor Query Performance Indexen bepalen de query-prestaties. Zonder juiste indexen scant MongoDB volledige 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' }); ``` Samengestelde indexen ondersteunen queries die filteren of sorteren op meerdere velden. De index `{ category: 1, price: -1 }` optimaliseert queries zoals `find({ category }).sort({ price: -1 })`. ## Veelgestelde Sollicitatievragen over NestJS en MongoDB Technische sollicitatiegesprekken testen zowel conceptueel begrip als praktische ervaring. Deze vragen verschijnen vaak bij senior backend posities. **V: Hoe behandelt Mongoose connection pooling?** Mongoose onderhoudt intern een connection pool. De `maxPoolSize` optie (standaard: 100) limiteert gelijktijdige verbindingen. Elke operatie haalt een verbinding uit de pool, voert deze uit en retourneert deze. Connection pooling vermijdt de overhead van het opzetten van nieuwe TCP-verbindingen per query. **V: Wanneer embedded documenten gebruiken in plaats van referenties?** Data embedden die bij elkaar hoort en beperkte groei heeft. Verzendadressen van een gebruiker (maximaal 5-10) embedden goed. Orderregels worden in het orderdocument geëmbed. Referenties passen bij onbegrensde relaties: bestellingen van een gebruiker over de jaren, of producten in een categorie. De regel: als de data 90% van de tijd samen wordt geladen en onder 16MB blijft, embedden. **V: Leg de $lookup stage van de aggregation pipeline uit.** De `$lookup` stage voert een left outer join uit tussen collections. Het matcht documenten uit een externe collection gebaseerd op veldgelijkheid of een aangepaste pipeline. Anders dan SQL joins wordt `$lookup` uitgevoerd tijdens de aggregatie en kan het extra filtering en projectie bevatten binnen de join. ```typescript // Example: Orders with customer details const pipeline: PipelineStage[] = [ { $lookup: { from: 'customers', localField: 'customerId', foreignField: '_id', as: 'customer', }, }, { $unwind: '$customer' }, ]; ``` **V: Hoe worden schema-migraties in MongoDB afgehandeld?** MongoDB schemas evolueren anders dan SQL. Gangbare strategieën: - Nieuwe velden toevoegen met standaardwaarden (achterwaarts compatibel) - Migratiescripts uitvoeren die bestaande documenten in batches bijwerken - Schema-versioning gebruiken met een `schemaVersion` veld - Mongoose middleware (`pre('save')`) kan documenten transformeren bij schrijven > **Sollicitatietip** > > Senior kandidaten leggen trade-offs uit. Junioren sommen features op. Wanneer gevraagd wordt naar embedded vs gerefereerde documenten, bespreek querypatronen, documentgroottelimieten (16MB) en updatefrequentie, niet alleen "het hangt ervan af". ## Foutafhandeling en Validatiepatronen Mongoose validatie draait voor save-operaties. Aangepaste validators behandelen bedrijfslogica. ```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(); }); ``` De `pre('save')` middleware draait voor elke save-operatie. Deze wordt gebruikt voor berekende velden, audit logging of cascaderende updates. ## Performance Monitoring met explain() De `explain()` methode onthult query-uitvoeringsplannen. Deze wordt gebruikt om ontbrekende indexen en trage queries te identificeren. ```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 } ``` Een verhouding van `totalDocsExamined` tot `nReturned` dicht bij 1 duidt op efficiënt indexgebruik. Hoge verhoudingen signaleren ontbrekende indexen of niet-selectieve queries. ## Kernpunten voor NestJS MongoDB Ontwikkeling - Configureer `maxPoolSize` op basis van verwachte gelijktijdigheid, standaard 100 past bij de meeste workloads - Gebruik `HydratedDocument` voor correcte TypeScript typing van Mongoose documenten - Roep `.exec()` aan op queries om native Promises te krijgen met accurate stack traces - Plaats `$match` stages vroeg in aggregation pipelines om verwerkte documenten te verminderen - Transacties vereisen replica sets, verifieer de deployment topologie voordat hierop wordt vertrouwd - Maak samengestelde indexen die overeenkomen met querypatronen: filtervelden eerst, sorteervelden daarna - Embed documenten met beperkte groei, refereer documenten die onbeperkt groeien - Monitor query-prestaties met `explain('executionStats')` tijdens ontwikkeling --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/nl/blog/node-nestjs/nestjs-mongodb-mongoose-guide