NestJS และ MongoDB ในปี 2026: Mongoose, Aggregation และคำถามสัมภาษณ์
เชี่ยวชาญ NestJS กับ MongoDB และ Mongoose 9 เรียนรู้การออกแบบ schema, aggregation pipeline และเตรียมตัวสัมภาษณ์เทคนิคด้วยตัวอย่างจริง

NestJS 12 ผสานกับ MongoDB ผ่าน Mongoose 9 ให้ stack ที่พร้อมใช้งานจริงสำหรับการสร้าง backend Node.js ที่ปรับขนาดได้ คู่มือนี้ครอบคลุมรูปแบบการออกแบบ schema, aggregation pipeline และคำถามสัมภาษณ์ที่แยกผู้สมัคร senior ออกจาก junior
Mongoose 9.9.4 ต้องการ Node.js 18+ และรองรับ MongoDB 6.0 ถึง 8.0 NestJS 12 มาพร้อม package ที่พร้อมใช้ ESM แต่ยังคงเข้ากันได้กับโปรเจกต์ CommonJS
การตั้งค่า Mongoose ในแอปพลิเคชัน NestJS 12
Package @nestjs/mongoose ผสาน Mongoose เข้ากับ dependency injection ของ NestJS ติดตั้ง dependency ที่จำเป็น:
# Install Mongoose integration
npm install @nestjs/mongoose mongooseลงทะเบียนการเชื่อมต่อใน root 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 {}เมธอด forRoot รับตัวเลือกการเชื่อมต่อ Mongoose ทั้งหมด การตั้งค่า maxPoolSize ป้องกันการหมดการเชื่อมต่อภายใต้โหลดสูง ซึ่งเป็นปัญหาทั่วไปใน production
การออกแบบ Schema ด้วย TypeScript Decorator
Mongoose schema ใน NestJS ใช้ decorator จาก @nestjs/mongoose แต่ละ schema จะ map ไปยัง 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 กำหนดข้อจำกัดของ field การตั้งค่า index: true บน field ที่ถูก query บ่อยจะปรับปรุงประสิทธิภาพการอ่านแต่ทำให้การเขียนช้าลง
ผู้สัมภาษณ์มักถามเกี่ยวกับ trade-off ระหว่าง embedded document และ reference Embedded document เหมาะกับข้อมูลที่เข้าถึงพร้อมกัน (โปรไฟล์ผู้ใช้ + การตั้งค่า) Reference เหมาะกับข้อมูลที่เติบโตไม่จำกัดหรือต้องการ query แยก (ผู้ใช้ + คำสั่งซื้อ)
รูปแบบ Repository ด้วย Injectable Service
NestJS ส่งเสริมการแยกลอจิก database ออกเป็น service Decorator @InjectModel ให้การเข้าถึง 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();
}
}การเรียก .exec() จะคืน Promise ที่ถูกต้องแทนที่จะเป็น Mongoose Query object สิ่งนี้สำคัญสำหรับพฤติกรรม async/await ที่ถูกต้องและ stack trace ของ error ที่แม่นยำ
Aggregation Pipeline สำหรับ Query ที่ซับซ้อน
MongoDB aggregation จัดการรายงาน การวิเคราะห์ และการแปลงข้อมูลที่ฐานข้อมูล 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();
}
}Aggregation pipeline ประมวลผล document ผ่านขั้นตอนตามลำดับ แต่ละขั้นตอนแปลง output สำหรับขั้นตอนถัดไป ขั้นตอน $match กรองตั้งแต่ต้นเพื่อลด document ที่ถูกประมวลผลโดยขั้นตอนถัดไป
พร้อมที่จะพิชิตการสัมภาษณ์ Node.js / NestJS แล้วหรือยังครับ?
ฝึกฝนด้วยตัวจำลองแบบโต้ตอบ, flashcards และแบบทดสอบเทคนิคครับ
Transaction สำหรับการดำเนินการหลาย Document
MongoDB 4.0+ รองรับ ACID transaction หลาย document ใช้ transaction เมื่อหลาย document ต้องอัปเดตแบบ atomic
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 จัดการ commit และ rollback โดยอัตโนมัติ หากการดำเนินการใดๆ throw error ทั้ง transaction จะถูกยกเลิก
Transaction ต้องการ replica set MongoDB หรือ sharded cluster Instance MongoDB standalone ไม่รองรับ transaction Atlas tier M0/M2/M5 รวม replica set เป็นค่าเริ่มต้น
กลยุทธ์ Indexing สำหรับประสิทธิภาพ Query
Index กำหนดประสิทธิภาพ query หากไม่มี index ที่เหมาะสม MongoDB จะ scan ทั้ง 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 รองรับ query ที่กรองหรือเรียงลำดับตามหลาย field Index { category: 1, price: -1 } เพิ่มประสิทธิภาพ query เช่น find({ category }).sort({ price: -1 })
คำถามสัมภาษณ์ทั่วไปเกี่ยวกับ NestJS และ MongoDB
การสัมภาษณ์เทคนิคทดสอบทั้งความเข้าใจแนวคิดและประสบการณ์จริง คำถามเหล่านี้ปรากฏบ่อยสำหรับตำแหน่ง backend senior
Q: Mongoose จัดการ connection pooling อย่างไร?
Mongoose รักษา connection pool ภายใน ตัวเลือก maxPoolSize (ค่าเริ่มต้น: 100) จำกัดการเชื่อมต่อพร้อมกัน แต่ละการดำเนินการจะเช็คเอาต์การเชื่อมต่อจาก pool ดำเนินการ และคืน Connection pooling หลีกเลี่ยงค่าใช้จ่ายของการสร้างการเชื่อมต่อ TCP ใหม่ต่อ query
Q: เมื่อไหร่ควรใช้ embedded document แทน reference?
Embed ข้อมูลที่เป็นของกันและมีการเติบโตจำกัด ที่อยู่จัดส่งของผู้ใช้ (สูงสุด 5-10) embed ได้ดี รายการในคำสั่งซื้อ embed ภายใน document คำสั่งซื้อ Reference เหมาะกับความสัมพันธ์ไม่จำกัด: คำสั่งซื้อของผู้ใช้ตลอดหลายปี หรือสินค้าในหมวดหมู่ กฎ: หากข้อมูลโหลดพร้อมกัน 90% ของเวลาและต่ำกว่า 16MB ให้ embed
Q: อธิบาย stage $lookup ใน aggregation pipeline
$lookup ทำ left outer join ระหว่าง collection มันจับคู่ document จาก collection ต่างประเทศตามความเท่าเทียมของ field หรือ pipeline ที่กำหนดเอง ไม่เหมือน join ของ SQL $lookup ดำเนินการระหว่าง aggregation และสามารถรวมการกรองและ projection เพิ่มเติมภายใน join
// Example: Orders with customer details
const pipeline: PipelineStage[] = [
{
$lookup: {
from: 'customers',
localField: 'customerId',
foreignField: '_id',
as: 'customer',
},
},
{ $unwind: '$customer' },
];Q: จัดการ schema migration ใน MongoDB อย่างไร?
Schema MongoDB พัฒนาแตกต่างจาก SQL กลยุทธ์ทั่วไป:
- เพิ่ม field ใหม่ด้วยค่าเริ่มต้น (เข้ากันได้ย้อนหลัง)
- รันสคริปต์ migration ที่อัปเดต document ที่มีอยู่เป็น batch
- ใช้ versioning schema ด้วย field
schemaVersion - Mongoose middleware (
pre('save')) สามารถแปลง document เมื่อเขียน
ผู้สมัคร senior อธิบาย trade-off Junior แค่ระบุฟีเจอร์ เมื่อถูกถามเกี่ยวกับ embedded vs reference ให้พูดคุยเกี่ยวกับรูปแบบ query ขีดจำกัดขนาด document (16MB) และความถี่ในการอัปเดต ไม่ใช่แค่ "ขึ้นอยู่กับ"
รูปแบบ Error Handling และ Validation
Mongoose validation ทำงานก่อนการดำเนินการ save Validator ที่กำหนดเองจัดการลอจิกธุรกิจ
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') ทำงานก่อนการดำเนินการ save แต่ละครั้ง ใช้สำหรับ field ที่คำนวณ audit logging หรือ cascading update
การตรวจสอบประสิทธิภาพด้วย explain()
เมธอด explain() เปิดเผยแผนการดำเนินการ query ใช้เพื่อระบุ index ที่หายไปและ query ที่ช้า
// 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 บ่งบอกการใช้ index ที่มีประสิทธิภาพ อัตราส่วนสูงบ่งบอก index ที่หายไปหรือ query ที่ไม่เลือกสรร
ประเด็นสำคัญสำหรับการพัฒนา NestJS MongoDB
- กำหนดค่า
maxPoolSizeตามความพร้อมกันที่คาดหวัง ค่าเริ่มต้น 100 เหมาะกับ workload ส่วนใหญ่ - ใช้
HydratedDocument<T>สำหรับ TypeScript typing ที่ถูกต้องของ Mongoose document - เรียก
.exec()บน query เพื่อรับ Promise native พร้อม stack trace ที่แม่นยำ - วาง stage
$matchตั้งแต่ต้นใน aggregation pipeline เพื่อลด document ที่ประมวลผล - Transaction ต้องการ replica set ตรวจสอบ topology การ deploy ก่อนพึ่งพามัน
- สร้าง compound index ที่ตรงกับรูปแบบ query: field กรองก่อน field เรียงลำดับหลัง
- Embed document ที่มีการเติบโตจำกัด reference document ที่เติบโตไม่จำกัด
- ตรวจสอบประสิทธิภาพ query ด้วย
explain('executionStats')ระหว่างการพัฒนา
เริ่มฝึกซ้อมเลย!
ทดสอบความรู้ของคุณด้วยตัวจำลองสัมภาษณ์และแบบทดสอบเทคนิคครับ
คุณหาบั๊กใน Node.js / NestJS เจอไหม
โค้ดจริงหนึ่งชิ้น บั๊กที่ซ่อนอยู่หนึ่งจุด วันละหนึ่งครั้ง ลองได้โดยไม่ต้องมีบัญชี

เขียนโดย
Anthony Fillion-Mailletผู้ก่อตั้ง SharpSkill
เป็นนักพัฒนาฟูลสแตกมากว่า 10 ปี ดูแล SharpSkill และรับผิดชอบทุกสิ่งที่เผยแพร่ที่นี่
อัปเดตเมื่อ 28 สิงหาคม 2569
แท็ก
แชร์
บทความที่เกี่ยวข้อง

NestJS และ Redis ในปี 2026: Caching, Sessions และคำถามสัมภาษณ์
คู่มือฉบับสมบูรณ์สำหรับการผสาน NestJS กับ Redis สำหรับ caching และการจัดการ session พร้อมคำถามสัมภาษณ์เพื่อเตรียมตัวสำหรับการสัมภาษณ์งาน Node.js

NestJS + Prisma: สแตกแบ็กเอนด์สมัยใหม่สำหรับ Node.js
คู่มือฉบับสมบูรณ์ในการสร้าง API แบ็กเอนด์สมัยใหม่ด้วย NestJS และ Prisma ครอบคลุมการตั้งค่า โมเดล เซอร์วิส ทรานแซกชัน และแนวปฏิบัติที่ดี

คำถามสัมภาษณ์ Backend Node.js: คู่มือฉบับสมบูรณ์ 2026
25 คำถามสัมภาษณ์ Backend Node.js ที่พบบ่อยที่สุด Event loop, async/await, streams, clustering และประสิทธิภาพอธิบายพร้อมคำตอบโดยละเอียด