NestJS và MongoDB trong 2026: Mongoose, Aggregation và Câu hỏi Phỏng vấn
Thành thạo NestJS với MongoDB và Mongoose 9. Học thiết kế schema, aggregation pipeline, và chuẩn bị phỏng vấn kỹ thuật với các ví dụ thực tế.

NestJS 12 kết hợp với MongoDB thông qua Mongoose 9 cung cấp một stack sẵn sàng cho production để xây dựng backend Node.js có khả năng mở rộng. Hướng dẫn này trình bày các pattern thiết kế schema, aggregation pipeline, và những câu hỏi phỏng vấn phân biệt ứng viên senior với junior.
Mongoose 9.9.4 yêu cầu Node.js 18+ và hỗ trợ MongoDB 6.0 đến 8.0. NestJS 12 cung cấp các package sẵn sàng ESM nhưng vẫn tương thích ngược với các dự án CommonJS.
Thiết lập Mongoose trong Ứng dụng NestJS 12
Package @nestjs/mongoose tích hợp Mongoose với dependency injection của NestJS. Cài đặt các dependency cần thiết:
# Install Mongoose integration
npm install @nestjs/mongoose mongooseĐăng ký kết nối trong module gốc:
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 {}Phương thức forRoot chấp nhận tất cả các tùy chọn kết nối Mongoose. Cấu hình maxPoolSize ngăn chặn việc cạn kiệt kết nối dưới tải cao, một vấn đề phổ biến trong môi trường production.
Thiết kế Schema với TypeScript Decorator
Mongoose schema trong NestJS sử dụng decorator từ @nestjs/mongoose. Mỗi schema ánh xạ đến một 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);Decorator @Prop định nghĩa các ràng buộc trường. Thiết lập index: true trên các trường được query thường xuyên cải thiện hiệu suất đọc nhưng làm chậm ghi.
Nhà tuyển dụng thường hỏi về sự đánh đổi giữa embedded document và reference. Embedded document phù hợp với dữ liệu được truy cập cùng nhau (hồ sơ người dùng + tùy chọn). Reference phù hợp với dữ liệu tăng trưởng không giới hạn hoặc yêu cầu query độc lập (người dùng + đơn hàng).
Pattern Repository với Injectable Service
NestJS khuyến khích tách logic database vào các service. Decorator @InjectModel cung cấp quyền truy cập vào Mongoose model.
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();
}
}Việc gọi .exec() trả về một Promise đúng nghĩa thay vì đối tượng Mongoose Query. Điều này quan trọng cho hành vi async/await chính xác và stack trace lỗi rõ ràng.
Aggregation Pipeline cho Query Phức tạp
MongoDB aggregation xử lý báo cáo, phân tích, và biến đổi dữ liệu mà database SQL giải quyết bằng join và 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();
}
}Aggregation pipeline xử lý document qua các giai đoạn tuần tự. Mỗi giai đoạn biến đổi output cho giai đoạn tiếp theo. Giai đoạn $match lọc sớm để giảm số document được xử lý bởi các giai đoạn sau.
Sẵn sàng chinh phục phỏng vấn Node.js / NestJS?
Luyện tập với mô phỏng tương tác, flashcards và bài kiểm tra kỹ thuật.
Transaction cho Thao tác Đa Document
MongoDB 4.0+ hỗ trợ transaction ACID đa document. Sử dụng transaction khi nhiều document cần được cập nhật nguyên tử.
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 xử lý commit và rollback tự động. Nếu bất kỳ thao tác nào throw lỗi, toàn bộ transaction bị hủy.
Transaction yêu cầu replica set MongoDB hoặc sharded cluster. Instance MongoDB standalone không hỗ trợ transaction. Các tier Atlas M0/M2/M5 bao gồm replica set theo mặc định.
Chiến lược Indexing cho Hiệu suất Query
Index quyết định hiệu suất query. Không có index phù hợp, MongoDB quét toàn bộ 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' });Compound index hỗ trợ các query lọc hoặc sắp xếp theo nhiều trường. Index { category: 1, price: -1 } tối ưu hóa các query như find({ category }).sort({ price: -1 }).
Câu hỏi Phỏng vấn Phổ biến về NestJS và MongoDB
Phỏng vấn kỹ thuật kiểm tra cả hiểu biết khái niệm và kinh nghiệm thực tế. Những câu hỏi này xuất hiện thường xuyên cho các vị trí backend senior.
Q: Mongoose xử lý connection pooling như thế nào?
Mongoose duy trì connection pool nội bộ. Tùy chọn maxPoolSize (mặc định: 100) giới hạn các kết nối đồng thời. Mỗi thao tác lấy một kết nối từ pool, thực thi, và trả lại. Connection pooling tránh chi phí thiết lập kết nối TCP mới cho mỗi query.
Q: Khi nào nên sử dụng embedded document thay vì reference?
Embed dữ liệu thuộc về nhau và có tăng trưởng giới hạn. Địa chỉ giao hàng của người dùng (tối đa 5-10) embed tốt. Các mục trong đơn hàng embed trong document đơn hàng. Reference phù hợp với quan hệ không giới hạn: đơn hàng của người dùng qua nhiều năm, hoặc sản phẩm trong danh mục. Quy tắc: nếu dữ liệu tải cùng nhau 90% thời gian và dưới 16MB, hãy embed.
Q: Giải thích giai đoạn $lookup trong aggregation pipeline.
$lookup thực hiện left outer join giữa các collection. Nó khớp document từ collection ngoại dựa trên bằng trường hoặc pipeline tùy chỉnh. Không giống join SQL, $lookup thực thi trong quá trình aggregation và có thể bao gồm lọc và projection bổ sung trong join.
// Example: Orders with customer details
const pipeline: PipelineStage[] = [
{
$lookup: {
from: 'customers',
localField: 'customerId',
foreignField: '_id',
as: 'customer',
},
},
{ $unwind: '$customer' },
];Q: Làm thế nào để xử lý schema migration trong MongoDB?
Schema MongoDB phát triển khác với SQL. Các chiến lược phổ biến:
- Thêm trường mới với giá trị mặc định (tương thích ngược)
- Chạy script migration cập nhật document hiện có theo batch
- Sử dụng versioning schema với trường
schemaVersion - Mongoose middleware (
pre('save')) có thể biến đổi document khi ghi
Ứng viên senior giải thích các đánh đổi. Junior chỉ liệt kê tính năng. Khi được hỏi về embedded vs reference, thảo luận về pattern query, giới hạn kích thước document (16MB), và tần suất cập nhật, không chỉ "tùy thuộc".
Pattern Xử lý Lỗi và Validation
Mongoose validation chạy trước các thao tác save. Validator tùy chỉnh xử lý logic nghiệp vụ.
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') chạy trước mỗi thao tác save. Sử dụng cho các trường tính toán, audit logging, hoặc cascading update.
Giám sát Hiệu suất với explain()
Phương thức explain() tiết lộ kế hoạch thực thi query. Sử dụng để xác định index bị thiếu và query chậm.
// 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
}Tỷ lệ totalDocsExamined trên nReturned gần 1 cho thấy sử dụng index hiệu quả. Tỷ lệ cao báo hiệu index bị thiếu hoặc query không chọn lọc.
Điểm Chính cho Phát triển NestJS MongoDB
- Cấu hình
maxPoolSizedựa trên mức độ đồng thời dự kiến, mặc định 100 phù hợp với hầu hết workload - Sử dụng
HydratedDocument<T>cho TypeScript typing chính xác trên Mongoose document - Gọi
.exec()trên query để nhận Promise native với stack trace chính xác - Đặt giai đoạn
$matchsớm trong aggregation pipeline để giảm document được xử lý - Transaction yêu cầu replica set, xác minh topology deployment trước khi dựa vào chúng
- Tạo compound index khớp với pattern query: trường lọc trước, trường sắp xếp sau
- Embed document có tăng trưởng giới hạn, reference document tăng trưởng không giới hạn
- Giám sát hiệu suất query với
explain('executionStats')trong quá trình phát triển
Bắt đầu luyện tập!
Kiểm tra kiến thức với mô phỏng phỏng vấn và bài kiểm tra kỹ thuật.
Bạn có tìm ra lỗi trong Node.js / NestJS không?
Một đoạn mã thật, một lỗi ẩn, mỗi ngày một lượt. Không cần tài khoản để thử.

Viết bởi
Anthony Fillion-MailletNgười sáng lập SharpSkill
Lập trình viên fullstack hơn 10 năm. Anh điều hành SharpSkill và chịu trách nhiệm về mọi nội dung đăng tại đây.
Cập nhật ngày 28 tháng 8, 2026
Thẻ
Chia sẻ
Bài viết liên quan

NestJS và Redis năm 2026: Caching, Sessions và Câu hỏi Phỏng vấn
Hướng dẫn toàn diện về tích hợp NestJS với Redis cho caching và quản lý session, kèm theo các câu hỏi phỏng vấn để chuẩn bị cho buổi phỏng vấn Node.js.

NestJS + Prisma: stack backend hiện đại cho Node.js
Hướng dẫn đầy đủ để xây dựng API backend hiện đại với NestJS và Prisma. Cấu hình, model, service, transaction và các best practice được giải thích chi tiết.

Cau Hoi Phong Van Backend Node.js: Huong Dan Day Du 2026
25 cau hoi phong van backend Node.js thuong gap nhat. Event loop, async/await, streams, clustering va hieu suat duoc giai thich chi tiet.