MongoDBAdvanced
Real Project: Blog
User, Post, Comment — তিনটি collection-এর সাথে populate, aggregation ও subdocument দিয়ে পূর্ণাঙ্গ ব্লগ — বাংলায়।
9টি অংশবাংলা ভাষায়
Schema ডিজাইন
models/Post.jsjs
js
import mongoose from "mongoose";
const commentSchema = new mongoose.Schema(
{
author: { type: mongoose.Schema.Types.ObjectId, ref: "User", required: true },
body: { type: String, required: true, minlength: 1 },
likes: [{ type: mongoose.Schema.Types.ObjectId, ref: "User" }],
},
{ timestamps: true }
);
const postSchema = new mongoose.Schema(
{
title: { type: String, required: true, maxlength: 120 },
slug: { type: String, unique: true, index: true },
body: { type: String, required: true },
author: { type: mongoose.Schema.Types.ObjectId, ref: "User", required: true },
tags: [String],
views: { type: Number, default: 0 },
comments: [commentSchema],
},
{ timestamps: true }
);
// slug স্বয়ংক্রিয় তৈরি
postSchema.pre("save", function (next) {
if (!this.slug) {
this.slug =
this.title.toLowerCase().replace(/[^a-z0-9]+/g, "-") +
"-" +
Math.random().toString(36).slice(2, 7);
}
next();
});
export default mongoose.model("Post", postSchema);Post + author populate
post controllerjs
js
// home: latest posts + author
router.get("/", async (req, res, next) => {
try {
const posts = await Post.find()
.populate("author", "name email")
.sort({ createdAt: -1 })
.limit(20)
.lean();
res.json(posts);
} catch (err) {
next(err);
}
});
// single post — views বাড়িয়ে fields
router.get("/:slug", async (req, res, next) => {
try {
const post = await Post.findOneAndUpdate(
{ slug: req.params.slug },
{ $inc: { views: 1 } },
{ new: true }
)
.populate({
path: "author",
select: "name",
})
.populate({
path: "comments.author",
select: "name",
});
if (!post) return res.status(404).json({ message: "Post নেই" });
res.json(post);
} catch (err) {
next(err);
}
});Comment যোগ (subdocument)
comment routejs
js
router.post("/:slug/comments", async (req, res, next) => {
try {
const post = await Post.findOne({ slug: req.params.slug });
if (!post) return res.status(404).json({ message: "Post নেই" });
post.comments.push({
author: req.body.author, // logged-in user ID
body: req.body.body,
});
await post.save(); // subdocument validation-ও হবে
res.status(201).json(post.comments);
} catch (err) {
next(err);
}
});Aggregation — top post
Stats pipelinejs
js
// সবচেয়ে বেশি comment-এর পোস্ট ৫টি
const topCommented = await Post.aggregate([
{ $project: { title: 1, commentCount: { $size: "$comments" }, views: 1 } },
{ $sort: { commentCount: -1, views: -1 } },
{ $limit: 5 },
]);
// tag ধরে গড় views
const tagStats = await Post.aggregate([
{ $unwind: "$tags" },
{
$group: {
_id: "$tags",
avgViews: { $avg: "$views" },
total: { $sum: 1 },
},
},
{ $sort: { total: -1 } },
]);Subdocument vs separate collection
Comment-গুলো post-এর subdocument রাখলে পড়া দ্রুত এবং populate লাগে। তবে comment অনেক এনে ব্লগে (লাখো+) আলাদা collection + $lookup বা separate ref ভালো — পরিস্থিতি অনুযায়ী ডিজাইন বেছে নিন।
শেষ প্যারায় নিজে practice করুন — কোড কপি করে নিজের এডিটরে চালিয়ে দেখুন।