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.

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.
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:
# Install Mongoose integration
npm install @nestjs/mongoose mongooseLa connessione viene registrata nel modulo root:
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. 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.
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);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.
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.
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();
}
}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.
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();
}
}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.
Pronto a superare i tuoi colloqui su Node.js / NestJS?
Pratica con i nostri simulatori interattivi, flashcards e test tecnici.
Transazioni per Operazioni Multi-Documento
MongoDB 4.0+ supporta transazioni ACID multi-documento. Le transazioni vengono utilizzate quando più documenti devono essere aggiornati atomicamente.
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();
}
}
}Il wrapper session.withTransaction gestisce commit e rollback automaticamente. Se qualsiasi operazione lancia un'eccezione, l'intera transazione viene annullata.
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.
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' });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.
// 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
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.
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();
});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.
// 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
}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
maxPoolSizein base alla concorrenza attesa, il default 100 è adatto per la maggior parte dei workload - Utilizzare
HydratedDocument<T>per il typing TypeScript corretto dei documenti Mongoose - Chiamare
.exec()sulle query per ottenere Promise native con stack trace accurati - Posizionare gli stadi
$matchpresto 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
Inizia a praticare!
Metti alla prova le tue conoscenze con i nostri simulatori di colloquio e test tecnici.
Sapresti trovare il bug in Node.js / NestJS?
Uno snippet reale, un bug nascosto, un tentativo al giorno. Senza account per provare.

Scritto da
Anthony Fillion-MailletFondatore di SharpSkill
Sviluppatore fullstack da oltre 10 anni. Guida SharpSkill e risponde di tutto ciò che vi viene pubblicato.
Aggiornato il 28 agosto 2026
Condividi
Articoli correlati

ListView JavaScript Performante nel 2026: Windowing, Virtualizzazione e Ottimizzazione delle Prestazioni
Scopri come creare ListView JavaScript ad alte prestazioni con windowing e virtualizzazione. TanStack Virtual, React Virtualized e tecniche avanzate per il 2026.

NestJS e Redis nel 2026: Caching, Sessioni e Domande per Colloqui Tecnici
Integrazione Redis in NestJS con @nestjs/cache-manager, gestione delle sessioni con connect-redis e domande frequenti nei colloqui per sviluppatori Node.js backend.

High Performance JavaScript ListView nel 2026: Tecniche di Virtualizzazione e Ottimizzazione
Scopri come sviluppare ListView ad alte prestazioni con JavaScript list virtualization e React Virtualized. Deep-dive nelle tecniche di ottimizzazione del rendering.