MongoDB / Эцсийн төсөл
Эцсийн төсөл
Энэ хичээлд курсийн туршид сурсан бүх ойлголтыг нэгтгэн блог платформын backend-ийг MongoDB + Mongoose ашиглан бүтээнэ. Schema дизайн, CRUD endpoint, aggregation pipeline гурвыг хамтад нь хэрэгжүүлнэ.
Юу бүтээх вэ?
Блог платформ — хэрэглэгчид нийтлэл нийтлэх, сэтгэгдэл үлдээх, статистик харах боломжтой.
Collection-ууд: users, posts, comments
Алхам 1: Schema дизайн
users
typescript
// models/User.ts
import mongoose, { Schema, Document } from "mongoose";
export interface IUser extends Document {
username: string;
email: string;
xp: number;
createdAt: Date;
updatedAt: Date;
level: number;
}
const userSchema = new Schema<IUser>(
{
username: {
type: String,
required: true,
unique: true,
trim: true,
minlength: 3,
},
email: { type: String, required: true, unique: true, lowercase: true },
xp: { type: Number, default: 0, min: 0 },
},
{ timestamps: true, toJSON: { virtuals: true } },
);
userSchema.virtual("level").get(function () {
return Math.floor(this.xp / 100) + 1;
});
export default mongoose.model<IUser>("User", userSchema);
posts
typescript
// models/Post.ts
import mongoose, { Schema, Document } from "mongoose";
export interface IPost extends Document {
title: string;
content: string;
authorId: mongoose.Types.ObjectId;
tags: string[];
published: boolean;
viewCount: number;
commentCount: number;
createdAt: Date;
updatedAt: Date;
}
const postSchema = new Schema<IPost>(
{
title: { type: String, required: true, trim: true, maxlength: 200 },
content: { type: String, required: true },
authorId: { type: Schema.Types.ObjectId, ref: "User", required: true },
tags: { type: [String], default: [] },
published: { type: Boolean, default: false },
viewCount: { type: Number, default: 0 },
commentCount: { type: Number, default: 0 },
},
{ timestamps: true },
);
// Байнга хэрэглэгдэх query-нүүдэд index
postSchema.index({ authorId: 1 });
postSchema.index({ published: 1, createdAt: -1 });
postSchema.index({ tags: 1 });
export default mongoose.model<IPost>("Post", postSchema);
comments
typescript
// models/Comment.ts
import mongoose, { Schema, Document } from "mongoose";
export interface IComment extends Document {
postId: mongoose.Types.ObjectId;
authorId: mongoose.Types.ObjectId;
content: string;
createdAt: Date;
}
const commentSchema = new Schema<IComment>(
{
postId: { type: Schema.Types.ObjectId, ref: "Post", required: true },
authorId: { type: Schema.Types.ObjectId, ref: "User", required: true },
content: { type: String, required: true, maxlength: 1000 },
},
{ timestamps: true },
);
commentSchema.index({ postId: 1, createdAt: -1 });
// Сэтгэгдэл нэмэгдэхэд Post-ийн commentCount нэмэгдүүл
commentSchema.post("save", async function () {
await mongoose
.model("Post")
.findByIdAndUpdate(this.postId, { $inc: { commentCount: 1 } });
});
export default mongoose.model<IComment>("Comment", commentSchema);
Алхам 2: CRUD endpoint-ууд
Нийтлэл үүсгэх
typescript
// POST /api/posts
async function createPost(req, res) {
const { title, content, tags } = req.body;
const authorId = req.user._id; // auth middleware-ээс авна
const post = await Post.create({
title,
content,
tags,
authorId,
});
// Зохиогчийн XP нэмэх
await User.findByIdAndUpdate(authorId, { $inc: { xp: 20 } });
res.status(201).json(post);
}
Нийтлэлийн жагсаалт + зохиогчийн мэдээлэл
typescript
// GET /api/posts?page=1&limit=10
async function getPosts(req, res) {
const page = Number(req.query.page) || 1;
const limit = Number(req.query.limit) || 10;
const posts = await Post.find({ published: true })
.populate("authorId", "username level")
.sort({ createdAt: -1 })
.skip((page - 1) * limit)
.limit(limit)
.select("title tags viewCount commentCount createdAt");
const total = await Post.countDocuments({ published: true });
res.json({
posts,
pagination: {
page,
limit,
total,
totalPages: Math.ceil(total / limit),
},
});
}
Нийтлэл унших + үзэлтийн тоо нэмэх
typescript
// GET /api/posts/:id
async function getPost(req, res) {
const post = await Post.findByIdAndUpdate(
req.params.id,
{ $inc: { viewCount: 1 } },
{ new: true },
).populate("authorId", "username level");
if (!post || !post.published) {
return res.status(404).json({ error: "Нийтлэл олдсонгүй" });
}
const comments = await Comment.find({ postId: post._id })
.populate("authorId", "username")
.sort({ createdAt: -1 })
.limit(20);
res.json({ post, comments });
}
Алхам 3: Aggregation — статистик
Хэрэглэгчийн профайл статистик
typescript
// GET /api/users/:id/stats
async function getUserStats(req, res) {
const userId = new mongoose.Types.ObjectId(req.params.id);
const stats = await Post.aggregate([
{ $match: { authorId: userId, published: true } },
{
$group: {
_id: null,
totalPosts: { $sum: 1 },
totalViews: { $sum: "$viewCount" },
totalComments: { $sum: "$commentCount" },
avgViews: { $avg: "$viewCount" },
},
},
{
$project: {
_id: 0,
totalPosts: 1,
totalViews: 1,
totalComments: 1,
avgViews: { $round: ["$avgViews", 0] },
},
},
]);
const user = await User.findById(userId).select("username xp level");
res.json({
user,
stats: stats[0] || {
totalPosts: 0,
totalViews: 0,
totalComments: 0,
avgViews: 0,
},
});
}
Хамгийн их үзэгдсэн tag-ууд
typescript
// GET /api/stats/tags
async function getTopTags(req, res) {
const topTags = await Post.aggregate([
{ $match: { published: true } },
{ $unwind: "$tags" }, // tags массивыг задлах
{
$group: {
_id: "$tags",
count: { $sum: 1 },
views: { $sum: "$viewCount" },
},
},
{ $sort: { count: -1 } },
{ $limit: 10 },
{
$project: {
_id: 0,
tag: "$_id",
count: 1,
views: 1,
},
},
]);
res.json(topTags);
}
Алхам 4: Бүтэц эмхэтгэх
код
blog-api/
├── models/
│ ├── User.ts
│ ├── Post.ts
│ └── Comment.ts
├── routes/
│ ├── posts.ts
│ ├── comments.ts
│ └── stats.ts
├── lib/
│ └── mongodb.ts ← connectDB()
└── index.ts
Курс дууслаа
Энэ курсийн явцад дараах зүйлсийг эзэмшлээ:
- MongoDB-ийн үндсэн ойлголтууд — document, collection, BSON
- CRUD үйлдлүүд — insert, find, update, delete
- Query operator, update operator
- Aggregation Pipeline —
$match,$group,$lookup,$project - Index — гүйцэтгэлийг сайжруулах
- Schema дизайн — embedding vs referencing
- Document validation, transaction
- MongoDB Atlas — cloud дээр ажиллуулах
- Mongoose — Node.js/TypeScript-тэй хосолж ашиглах
Одоо чи MongoDB-г бодит аппликейшнд ашиглахад бэлэн боллоо.