NestJS та MongoDB у 2026: Mongoose, Агрегації та Питання на Співбесідах
Опануйте NestJS з MongoDB та Mongoose 9. Вивчіть патерни проектування схем, конвеєри агрегацій та підготуйтесь до технічних співбесід з практичними прикладами.

NestJS 12 у поєднанні з MongoDB через Mongoose 9 забезпечує готовий до продакшену стек для побудови масштабованих Node.js бекендів. Цей посібник охоплює патерни проектування схем, конвеєри агрегацій та питання на співбесідах, які відрізняють senior-спеціалістів від junior.
Mongoose 9.9.4 вимагає Node.js 18+ та підтримує MongoDB від версії 6.0 до 8.0. NestJS 12 постачає ESM-ready пакети, залишаючись сумісним з CommonJS проектами.
Налаштування Mongoose в Додатку NestJS 12
Пакет @nestjs/mongoose інтегрує Mongoose з ін'єкцією залежностей NestJS. Необхідно встановити потрібні залежності:
# Install Mongoose integration
npm install @nestjs/mongoose mongooseРеєстрація підключення в кореневому модулі:
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 {}Метод forRoot приймає всі опції підключення Mongoose. Налаштування maxPoolSize запобігає вичерпанню підключень під навантаженням, що є поширеною проблемою в продакшені.
Проектування Схем з Декораторами TypeScript
Схеми Mongoose в NestJS використовують декоратори з @nestjs/mongoose. Кожна схема відображається на колекцію 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);Декоратор @Prop визначає обмеження полів. Налаштування index: true на полях, що часто запитуються, покращує продуктивність читання ціною повільнішого запису.
Інтерв'юери запитують про компроміс між вбудованими документами та посиланнями. Вбудовані документи підходять для даних, що отримуються разом (профіль користувача + налаштування). Посилання підходять для даних, що зростають необмежено або потребують незалежних запитів (користувач + замовлення).
Патерн Repository з Ін'єктованими Сервісами
NestJS заохочує відокремлення логіки бази даних у сервіси. Декоратор @InjectModel надає доступ до моделі 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();
}
}Виклик .exec() повертає належний Promise замість об'єкта Mongoose Query. Це важливо для коректної поведінки async/await та стеків викликів помилок.
Конвеєри Агрегацій для Складних Запитів
Агрегації MongoDB обробляють звітність, аналітику та трансформації даних, які SQL бази вирішують за допомогою JOIN та 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();
}
}Конвеєри агрегацій обробляють документи через етапи послідовно. Кожен етап трансформує вихід для наступного етапу. Етап $match фільтрує рано, щоб зменшити кількість документів, що обробляються наступними етапами.
Готовий до співбесід з Node.js / NestJS?
Практикуйся з нашими інтерактивними симуляторами, flashcards та технічними тестами.
Транзакції для Багатодокументних Операцій
MongoDB 4.0+ підтримує багатодокументні ACID транзакції. Транзакції слід використовувати, коли кілька документів повинні оновлюватися атомарно.
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();
}
}
}Обгортка session.withTransaction автоматично обробляє commit та rollback. Якщо будь-яка операція викине виняток, вся транзакція скасовується.
Транзакції вимагають replica set MongoDB або sharded cluster. Автономні екземпляри MongoDB не підтримують транзакції. Рівні Atlas M0/M2/M5 за замовчуванням включають replica sets.
Стратегії Індексування для Продуктивності Запитів
Індекси визначають продуктивність запитів. Без належних індексів MongoDB сканує цілі колекції.
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' });Складені індекси підтримують запити, що фільтрують або сортують за кількома полями. Індекс { category: 1, price: -1 } оптимізує запити типу find({ category }).sort({ price: -1 }).
Поширені Питання на Співбесідах про NestJS та MongoDB
Технічні співбесіди перевіряють як концептуальне розуміння, так і практичний досвід. Ці питання часто з'являються на позиціях senior backend.
П: Як Mongoose обробляє пул з'єднань?
Mongoose внутрішньо підтримує пул з'єднань. Опція maxPoolSize (за замовчуванням: 100) обмежує кількість одночасних з'єднань. Кожна операція бере з'єднання з пулу, виконується та повертає його. Пулінг з'єднань уникає накладних витрат на встановлення нових TCP з'єднань для кожного запиту.
П: Коли слід використовувати вбудовані документи замість посилань?
Вбудовуйте дані, що належать разом та мають обмежене зростання. Адреси доставки користувача (максимум 5-10) добре вбудовуються. Позиції замовлення вбудовуються в документ замовлення. Посилання підходять для необмежених зв'язків: замовлення користувача за роки або продукти в категорії. Правило: якщо дані завантажуються разом у 90% випадків і залишаються під 16MB, вбудовуйте їх.
П: Поясніть етап $lookup в конвеєрі агрегації.
Етап $lookup виконує left outer join між колекціями. Він зіставляє документи із зовнішньої колекції на основі рівності полів або користувацького конвеєра. На відміну від SQL join, $lookup виконується під час агрегації та може включати додаткову фільтрацію та проекцію всередині join.
// Example: Orders with customer details
const pipeline: PipelineStage[] = [
{
$lookup: {
from: 'customers',
localField: 'customerId',
foreignField: '_id',
as: 'customer',
},
},
{ $unwind: '$customer' },
];П: Як ви обробляєте міграції схем у MongoDB?
Схеми MongoDB еволюціонують інакше, ніж SQL. Поширені стратегії:
- Додавання нових полів зі значеннями за замовчуванням (зворотно сумісне)
- Запуск скриптів міграції, що оновлюють існуючі документи пакетами
- Використання версіонування схем з полем
schemaVersion - Middleware Mongoose (
pre('save')) може трансформувати документи при записі
Senior-кандидати пояснюють компроміси. Junior-и перелічують функції. Коли запитують про вбудовані vs посилальні документи, обговорюйте патерни запитів, обмеження розміру документів (16MB) та частоту оновлень, а не просто "це залежить".
Патерни Обробки Помилок та Валідації
Валідація Mongoose запускається перед операціями збереження. Користувацькі валідатори обробляють бізнес-логіку.
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') запускається перед кожною операцією збереження. Використовується для обчислюваних полів, аудит-логування або каскадних оновлень.
Моніторинг Продуктивності з explain()
Метод explain() розкриває плани виконання запитів. Використовується для виявлення відсутніх індексів та повільних запитів.
// 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
}Співвідношення totalDocsExamined до nReturned близьке до 1 вказує на ефективне використання індексу. Високі значення сигналізують про відсутні індекси або неселективні запити.
Ключові Висновки для Розробки NestJS MongoDB
- Налаштовуйте
maxPoolSizeна основі очікуваної конкурентності, за замовчуванням 100 підходить для більшості навантажень - Використовуйте
HydratedDocument<T>для правильної типізації TypeScript документів Mongoose - Викликайте
.exec()на запитах для отримання нативних Promise з точними стеками викликів - Розміщуйте етапи
$matchрано в конвеєрах агрегацій для зменшення оброблюваних документів - Транзакції вимагають replica sets, перевірте топологію розгортання перед використанням
- Створюйте складені індекси, що відповідають патернам запитів: спочатку поля фільтрації, потім поля сортування
- Вбудовуйте документи з обмеженим зростанням, посилайтесь на документи з необмеженим зростанням
- Моніторте продуктивність запитів за допомогою
explain('executionStats')під час розробки
Починай практикувати!
Перевір свої знання з нашими симуляторами співбесід та технічними тестами.
Чи знайдеш ти помилку в Node.js / NestJS?
Справжній фрагмент коду, прихована помилка, одна спроба на день. Щоб спробувати, акаунт не потрібен.

Автор:
Anthony Fillion-MailletЗасновник SharpSkill
Fullstack-розробник понад 10 років. Керує SharpSkill і відповідає за все, що тут публікується.
Оновлено 28 серпня 2026 р.
Теги
Поділитися
Пов'язані статті

NestJS + Prisma: сучасний бекенд-стек для Node.js
Повний посібник зі створення сучасного бекенд-API з NestJS і Prisma. Налаштування, моделі, сервіси, транзакції та найкращі практики.

Питання на співбесіді з Node.js Backend: Повний посібник 2026
25 найпоширеніших питань на співбесіді з Node.js backend. Event loop, async/await, потоки, кластеризація та продуктивність з детальними відповідями.

NestJS: Створення повноцінного REST API
Повний посібник зі створення професійного REST API з NestJS. Контролери, сервіси, модулі, валідація з class-validator та обробка помилок з практичними прикладами.