NestJS i MongoDB w 2026: Mongoose, Agregacje i Pytania Rekrutacyjne
Opanuj NestJS z MongoDB i Mongoose 9. Poznaj wzorce projektowania schematów, potoki agregacji i przygotuj się do rozmów kwalifikacyjnych z praktycznymi przykładami.

NestJS 12 w połączeniu z MongoDB poprzez Mongoose 9 stanowi produkcyjnie gotowy stos do budowania skalowalnych backendów w Node.js. Ten przewodnik obejmuje wzorce projektowania schematów, potoki agregacji oraz pytania rekrutacyjne, które odróżniają seniorów od juniorów.
Mongoose 9.9.4 wymaga Node.js 18+ i wspiera MongoDB od wersji 6.0 do 8.0. NestJS 12 dostarcza pakiety gotowe na ESM, pozostając jednocześnie wstecznie kompatybilnym z projektami CommonJS.
Konfiguracja Mongoose w Aplikacji NestJS 12
Pakiet @nestjs/mongoose integruje Mongoose z wstrzykiwaniem zależności NestJS. Należy zainstalować wymagane zależności:
# Install Mongoose integration
npm install @nestjs/mongoose mongooseRejestracja połączenia w głównym module:
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 {}Metoda forRoot akceptuje wszystkie opcje połączenia Mongoose. Ustawienie maxPoolSize zapobiega wyczerpaniu połączeń pod obciążeniem, co jest częstym problemem produkcyjnym.
Projektowanie Schematów z Dekoratorami TypeScript
Schematy Mongoose w NestJS wykorzystują dekoratory z @nestjs/mongoose. Każdy schemat mapuje się na kolekcję 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);Dekorator @Prop definiuje ograniczenia pól. Ustawienie index: true na często odpytywanych polach poprawia wydajność odczytu kosztem wolniejszych zapisów.
Rekruterzy pytają o kompromis między osadzonymi dokumentami a referencjami. Osadzone dokumenty sprawdzają się przy danych pobieranych razem (profil użytkownika + preferencje). Referencje pasują do danych, które rosną nieograniczenie lub wymagają niezależnych zapytań (użytkownik + zamówienia).
Wzorzec Repository z Wstrzykiwanymi Serwisami
NestJS zachęca do separacji logiki bazodanowej w serwisach. Dekorator @InjectModel zapewnia dostęp do modelu 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();
}
}Wywołanie .exec() zwraca właściwą Promise zamiast obiektu Mongoose Query. Ma to znaczenie dla poprawnego zachowania async/await i śladów stosu błędów.
Potoki Agregacji dla Złożonych Zapytań
Agregacje MongoDB obsługują raportowanie, analitykę i transformacje danych, które bazy SQL rozwiązują za pomocą JOIN i 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();
}
}Potoki agregacji przetwarzają dokumenty przez etapy sekwencyjnie. Każdy etap transformuje wyjście dla następnego etapu. Etap $match filtruje wcześnie, aby zredukować liczbę dokumentów przetwarzanych przez kolejne etapy.
Gotowy na rozmowy o Node.js / NestJS?
Ćwicz z naszymi interaktywnymi symulatorami, flashcards i testami technicznymi.
Transakcje dla Operacji Wielodokumentowych
MongoDB 4.0+ wspiera wielodokumentowe transakcje ACID. Transakcji należy używać, gdy wiele dokumentów musi być aktualizowanych atomowo.
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();
}
}
}Wrapper session.withTransaction automatycznie obsługuje commit i rollback. Jeśli jakakolwiek operacja rzuci wyjątek, cała transakcja zostaje przerwana.
Transakcje wymagają replica set MongoDB lub klastra shardowanego. Samodzielne instancje MongoDB nie wspierają transakcji. Warstwy Atlas M0/M2/M5 domyślnie zawierają replica sets.
Strategie Indeksowania dla Wydajności Zapytań
Indeksy determinują wydajność zapytań. Bez odpowiednich indeksów MongoDB skanuje całe kolekcje.
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' });Indeksy złożone wspierają zapytania filtrujące lub sortujące po wielu polach. Indeks { category: 1, price: -1 } optymalizuje zapytania typu find({ category }).sort({ price: -1 }).
Typowe Pytania Rekrutacyjne o NestJS i MongoDB
Rozmowy techniczne sprawdzają zarówno zrozumienie koncepcyjne, jak i praktyczne doświadczenie. Te pytania pojawiają się często na stanowiskach seniorskich backend.
P: Jak Mongoose obsługuje pooling połączeń?
Mongoose wewnętrznie utrzymuje pulę połączeń. Opcja maxPoolSize (domyślnie: 100) ogranicza liczbę równoczesnych połączeń. Każda operacja pobiera połączenie z puli, wykonuje się i je zwraca. Pooling połączeń eliminuje narzut tworzenia nowych połączeń TCP na każde zapytanie.
P: Kiedy należy używać osadzonych dokumentów zamiast referencji?
Osadzaj dane, które należą do siebie i mają ograniczony wzrost. Adresy wysyłkowe użytkownika (maksymalnie 5-10) dobrze się osadzają. Pozycje zamówienia osadzają się w dokumencie zamówienia. Referencje pasują do nieograniczonych relacji: zamówienia użytkownika przez lata lub produkty w kategorii. Zasada: jeśli dane ładują się razem w 90% przypadków i mieszczą się w 16MB, osadź je.
P: Wyjaśnij etap $lookup w potoku agregacji.
Etap $lookup wykonuje left outer join między kolekcjami. Dopasowuje dokumenty z obcej kolekcji na podstawie równości pól lub niestandardowego potoku. W odróżnieniu od SQL join, $lookup wykonuje się podczas agregacji i może zawierać dodatkowe filtrowanie i projekcję wewnątrz joina.
// Example: Orders with customer details
const pipeline: PipelineStage[] = [
{
$lookup: {
from: 'customers',
localField: 'customerId',
foreignField: '_id',
as: 'customer',
},
},
{ $unwind: '$customer' },
];P: Jak obsługiwać migracje schematów w MongoDB?
Schematy MongoDB ewoluują inaczej niż SQL. Typowe strategie:
- Dodawanie nowych pól z wartościami domyślnymi (wstecznie kompatybilne)
- Uruchamianie skryptów migracji aktualizujących istniejące dokumenty partiami
- Użycie wersjonowania schematów z polem
schemaVersion - Middleware Mongoose (
pre('save')) może transformować dokumenty podczas zapisu
Kandydaci seniorzy wyjaśniają kompromisy. Juniorzy wymieniają funkcje. Pytani o osadzone vs referencyjne dokumenty, dyskutuj wzorce zapytań, limity rozmiaru dokumentów (16MB) i częstotliwość aktualizacji, nie tylko "to zależy".
Wzorce Obsługi Błędów i Walidacji
Walidacja Mongoose uruchamia się przed operacjami zapisu. Niestandardowe walidatory obsługują logikę biznesową.
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();
});Middleware pre('save') uruchamia się przed każdą operacją zapisu. Służy do pól wyliczanych, logowania audytu lub kaskadowych aktualizacji.
Monitorowanie Wydajności z explain()
Metoda explain() ujawnia plany wykonania zapytań. Służy do identyfikacji brakujących indeksów i wolnych zapytań.
// 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
}Stosunek totalDocsExamined do nReturned bliski 1 wskazuje na efektywne użycie indeksu. Wysokie wartości sygnalizują brakujące indeksy lub nieselektywne zapytania.
Kluczowe Wnioski dla Rozwoju NestJS MongoDB
- Konfiguruj
maxPoolSizew oparciu o oczekiwaną współbieżność, domyślne 100 sprawdza się w większości obciążeń - Używaj
HydratedDocument<T>dla prawidłowego typowania TypeScript dokumentów Mongoose - Wywołuj
.exec()na zapytaniach, aby uzyskać natywne Promise z dokładnymi śladami stosu - Umieszczaj etapy
$matchwcześnie w potokach agregacji, aby zredukować liczbę przetwarzanych dokumentów - Transakcje wymagają replica sets, zweryfikuj topologię wdrożenia przed poleganiem na nich
- Twórz indeksy złożone pasujące do wzorców zapytań: pola filtrujące najpierw, pola sortujące potem
- Osadzaj dokumenty z ograniczonym wzrostem, referencjonuj dokumenty rosnące nieograniczenie
- Monitoruj wydajność zapytań za pomocą
explain('executionStats')podczas rozwoju
Zacznij ćwiczyć!
Sprawdź swoją wiedzę z naszymi symulatorami rozmów i testami technicznymi.
Znajdziesz błąd w Node.js / NestJS?
Prawdziwy fragment kodu, ukryty błąd, jedna próba dziennie. Bez konta, żeby spróbować.

Autor:
Anthony Fillion-MailletZałożyciel SharpSkill
Programista fullstack od ponad 10 lat. Prowadzi SharpSkill i odpowiada za wszystko, co się tu ukazuje.
Zaktualizowano 28 sierpnia 2026
Tagi
Udostępnij
Powiązane artykuły

NestJS + Prisma: nowoczesny stack backendowy dla Node.js
Kompletny przewodnik po budowaniu nowoczesnego API backendowego z NestJS i Prismą. Konfiguracja, modele, serwisy, transakcje i dobre praktyki.

Pytania na rozmowie kwalifikacyjnej z Node.js Backend: Kompletny przewodnik 2026
25 najczęściej zadawanych pytań na rozmowie kwalifikacyjnej z Node.js backend. Event loop, async/await, strumienie, klasteryzacja i wydajność z szczegółowymi odpowiedziami.

NestJS: Budowanie kompletnego REST API
Kompletny przewodnik po budowaniu profesjonalnego REST API z NestJS. Kontrolery, serwisy, moduły, walidacja z class-validator i obsługa błędów z praktycznymi przykładami.