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.

NestJS en MongoDB in 2026: Mongoose, Aggregaties en Sollicitatievragen

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:

app.module.tstypescript
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. 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.

user.schema.tstypescript
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);

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.

user.service.tstypescript
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();
  }
}

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.

analytics.service.tstypescript
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 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.

Klaar om je Node.js / NestJS gesprekken te halen?

Oefen met onze interactieve simulatoren, flashcards en technische tests.

Transacties voor Multi-Document Operaties

MongoDB 4.0+ ondersteunt multi-document ACID transacties. Transacties worden gebruikt wanneer meerdere documenten atomisch moeten worden bijgewerkt.

transfer.service.tstypescript
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();
    }
  }
}

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.

product.schema.tstypescript
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' });

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.

order.schema.tstypescript
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();
});

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<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
}

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<T> 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

Begin met oefenen!

Test je kennis met onze gespreksimulatoren en technische tests.

Dagelijkse challenge

Zie jij de bug in Node.js / NestJS?

Een echt codefragment, een verborgen bug, één poging per dag. Zonder account uit te proberen.

Anthony Fillion-Maillet

Geschreven door

Anthony Fillion-Maillet

Oprichter van SharpSkill

Al meer dan 10 jaar fullstack-ontwikkelaar. Hij leidt SharpSkill en staat in voor alles wat hier verschijnt.

Bijgewerkt op 28 augustus 2026

Delen

Gerelateerde artikelen