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-г бодит аппликейшнд ашиглахад бэлэн боллоо.