# NestJS e MongoDB nel 2026: Mongoose, Aggregazioni e Domande da Colloquio > Guida pratica a NestJS 12 con MongoDB e Mongoose 9: design degli schema, pipeline di aggregazione, transazioni e domande tecniche per colloqui backend. - Published: 2026-08-28 - Updated: 2026-08-28 - Author: Anthony Fillion-Maillet - Reading time: 5 min --- NestJS 12 combinato con MongoDB tramite Mongoose 9 fornisce uno stack pronto per la produzione per costruire backend Node.js scalabili. Questa guida copre i pattern di design degli schema, le pipeline di aggregazione e le domande da colloquio che distinguono i candidati senior dai junior. > **Riferimento Rapido** > > Mongoose 9.9.4 richiede Node.js 18+ e supporta MongoDB dalla versione 6.0 alla 8.0. NestJS 12 distribuisce pacchetti pronti per ESM ma rimane retrocompatibile con i progetti CommonJS. ## Configurazione di Mongoose in un'Applicazione NestJS 12 Il pacchetto `@nestjs/mongoose` integra Mongoose con la dependency injection di NestJS. L'installazione delle dipendenze richieste avviene tramite npm: ```bash # Install Mongoose integration npm install @nestjs/mongoose mongoose ``` La connessione viene registrata nel modulo root: ```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 {} ``` Il metodo `forRoot` accetta tutte le [opzioni di connessione Mongoose](https://mongoosejs.com/docs/connections.html). L'impostazione `maxPoolSize` previene l'esaurimento delle connessioni sotto carico, un problema comune in produzione. ## Design degli Schema con Decoratori TypeScript Gli schema Mongoose in NestJS utilizzano decoratori da `@nestjs/mongoose`. Ogni schema viene mappato a una collection MongoDB. ```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); ``` Il decoratore `@Prop` definisce i vincoli dei campi. Impostare `index: true` sui campi interrogati frequentemente migliora le prestazioni di lettura a scapito di scritture più lente. > **Approfondimento da Colloquio** > > Gli intervistatori chiedono del trade-off tra documenti incorporati e riferimenti. I documenti incorporati sono adatti per dati accessibili insieme (profilo utente + preferenze). I riferimenti sono appropriati per dati che crescono illimitatamente o richiedono query indipendenti (utente + ordini). ## Pattern Repository con Servizi Injectable NestJS incoraggia la separazione della logica database in servizi. Il decoratore `@InjectModel` fornisce accesso al model Mongoose. ```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(); } } ``` Chiamare `.exec()` restituisce una Promise nativa invece di un oggetto Query Mongoose. Questo è importante per il corretto comportamento async/await e gli stack trace degli errori. ## Pipeline di Aggregazione per Query Complesse Le aggregazioni MongoDB gestiscono reporting, analytics e trasformazioni dati che i database SQL risolvono con join e 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(); } } ``` Le pipeline di aggregazione processano i documenti sequenzialmente attraverso stadi. Ogni stadio trasforma l'output per lo stadio successivo. Lo stadio `$match` filtra presto per ridurre i documenti processati dagli stadi successivi. ## Transazioni per Operazioni Multi-Documento MongoDB 4.0+ supporta transazioni ACID multi-documento. Le transazioni vengono utilizzate quando più documenti devono essere aggiornati atomicamente. ```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(); } } } ``` Il wrapper `session.withTransaction` gestisce commit e rollback automaticamente. Se qualsiasi operazione lancia un'eccezione, l'intera transazione viene annullata. > **Nota per la Produzione** > > Le transazioni richiedono un replica set MongoDB o un cluster sharded. Le istanze MongoDB standalone non supportano le transazioni. I tier Atlas M0/M2/M5 includono replica set di default. ## Strategie di Indicizzazione per le Prestazioni delle Query Gli indici determinano le prestazioni delle query. Senza indici appropriati, MongoDB scansiona intere collection. ```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' }); ``` Gli indici composti supportano query che filtrano o ordinano per campi multipli. L'indice `{ category: 1, price: -1 }` ottimizza query come `find({ category }).sort({ price: -1 })`. ## Domande Comuni da Colloquio su NestJS e MongoDB I colloqui tecnici testano sia la comprensione concettuale che l'esperienza pratica. Queste domande appaiono frequentemente per posizioni backend senior. **D: Come gestisce Mongoose il connection pooling?** Mongoose mantiene internamente un connection pool. L'opzione `maxPoolSize` (default: 100) limita le connessioni concorrenti. Ogni operazione preleva una connessione dal pool, la esegue e la restituisce. Il connection pooling evita l'overhead di stabilire nuove connessioni TCP per ogni query. **D: Quando utilizzare documenti incorporati invece di riferimenti?** Incorporare dati che appartengono insieme e hanno crescita limitata. Gli indirizzi di spedizione di un utente (massimo 5-10) si incorporano bene. Le righe d'ordine vengono incorporate nel documento dell'ordine. I riferimenti sono adatti per relazioni illimitate: gli ordini di un utente nel corso degli anni, o i prodotti in una categoria. La regola: se i dati vengono caricati insieme il 90% delle volte e restano sotto i 16MB, incorporarli. **D: Spiegare lo stadio $lookup della pipeline di aggregazione.** Lo stadio `$lookup` esegue un left outer join tra collection. Effettua il match di documenti da una collection esterna basandosi sull'uguaglianza dei campi o una pipeline personalizzata. A differenza dei join SQL, `$lookup` viene eseguito durante l'aggregazione e può includere filtraggio e proiezione aggiuntivi all'interno del join. ```typescript // Example: Orders with customer details const pipeline: PipelineStage[] = [ { $lookup: { from: 'customers', localField: 'customerId', foreignField: '_id', as: 'customer', }, }, { $unwind: '$customer' }, ]; ``` **D: Come vengono gestite le migrazioni degli schema in MongoDB?** Gli schema MongoDB evolvono diversamente da SQL. Strategie comuni: - Aggiungere nuovi campi con valori di default (retrocompatibile) - Eseguire script di migrazione che aggiornano i documenti esistenti in batch - Utilizzare il versionamento degli schema con un campo `schemaVersion` - Il middleware Mongoose (`pre('save')`) può trasformare i documenti in scrittura > **Suggerimento per il Colloquio** > > I candidati senior spiegano i trade-off. I junior elencano le feature. Quando viene chiesto di documenti incorporati vs riferimenti, discutere i pattern di query, i limiti di dimensione dei documenti (16MB) e la frequenza di aggiornamento, non solo "dipende". ## Gestione degli Errori e Pattern di Validazione La validazione Mongoose viene eseguita prima delle operazioni di salvataggio. I validatori personalizzati gestiscono la logica di business. ```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(); }); ``` Il middleware `pre('save')` viene eseguito prima di ogni operazione di salvataggio. Viene utilizzato per campi calcolati, audit logging o aggiornamenti a cascata. ## Monitoraggio delle Prestazioni con explain() Il metodo `explain()` rivela i piani di esecuzione delle query. Viene utilizzato per identificare indici mancanti e query lente. ```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 } ``` Un rapporto di `totalDocsExamined` rispetto a `nReturned` vicino a 1 indica un utilizzo efficiente degli indici. Rapporti elevati segnalano indici mancanti o query non selettive. ## Punti Chiave per lo Sviluppo NestJS MongoDB - Configurare `maxPoolSize` in base alla concorrenza attesa, il default 100 è adatto per la maggior parte dei workload - Utilizzare `HydratedDocument` per il typing TypeScript corretto dei documenti Mongoose - Chiamare `.exec()` sulle query per ottenere Promise native con stack trace accurati - Posizionare gli stadi `$match` presto nelle pipeline di aggregazione per ridurre i documenti processati - Le transazioni richiedono replica set, verificare la topologia del deployment prima di dipenderne - Creare indici composti che corrispondano ai pattern delle query: campi di filtro prima, campi di ordinamento dopo - Incorporare documenti con crescita limitata, riferire documenti che crescono illimitatamente - Monitorare le prestazioni delle query con `explain('executionStats')` durante lo sviluppo --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/it/blog/node-nestjs/nestjs-mongodb-mongoose-guide