Kritim Yantra
Aug 03, 2025
Ever tried building a blog platform and ended up with messy, unmaintainable code? You're not alone. A well-structured database design is the backbone of any successful blog system.
In this guide, we’ll architect a full-fledged Blog Management System with:
✅ User roles (Admin, Writers, Editors)
✅ Permissions & Access Control
✅ Authentication (Login/Register)
✅ Blog Analytics
✅ Optimized MongoDB Schema Design
By the end, you’ll have a production-ready Express.js API with a structured MongoDB database.
users
CollectionStores all user accounts (Admins, Writers, Editors).
{
_id: ObjectId("..."),
username: "john_doe",
email: "john@example.com",
password: "$2a$10$hashed...", // bcrypt encrypted
role: "writer", // "admin", "editor", "writer"
isActive: true,
createdAt: ISODate("2024-05-20"),
lastLogin: ISODate("2024-05-21")
}
Indexes:
email: 1
(Unique) role: 1
(For role-based queries)roles
CollectionDefines permissions for each role (Admin, Writer, Editor).
{
_id: ObjectId("..."),
name: "writer",
permissions: [
"create:post",
"edit:own_post",
"delete:own_post"
]
}
Example Roles:
Role | Permissions |
---|---|
Admin | * (All permissions) |
Editor | edit:any_post , publish:post |
Writer | create:post , edit:own_post |
posts
CollectionStores blog posts with metadata.
{
_id: ObjectId("..."),
title: "MongoDB Best Practices",
slug: "mongodb-best-practices", // SEO-friendly URL
content: "...",
author: ObjectId("..."), // Ref to `users._id`
status: "published", // "draft", "archived"
tags: ["mongodb", "database"],
createdAt: ISODate("2024-05-20"),
updatedAt: ISODate("2024-05-21"),
views: 1250 // For analytics
}
Indexes:
slug: 1
(Unique) author: 1
tags: 1
(For tag filtering)analytics
CollectionTracks post views, user engagement.
{
_id: ObjectId("..."),
postId: ObjectId("..."), // Ref to `posts._id`
date: ISODate("2024-05-21"),
views: 50,
reads: 30 // Users who read full post
}
Indexes:
postId: 1, date: 1
(For time-based analytics)Endpoint: POST /api/auth/register
// Input Validation (Joi)
{
username: Joi.string().required(),
email: Joi.string().email().required(),
password: Joi.string().min(6).required(),
role: Joi.string().valid("writer", "editor") // Admins created manually
}
// Password hashing (bcrypt)
const hashedPassword = await bcrypt.hash(password, 10);
const user = await User.create({ username, email, password: hashedPassword, role });
Endpoint: POST /api/auth/login
// Check user exists
const user = await User.findOne({ email });
if (!user) throw new Error("Invalid credentials");
// Verify password
const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch) throw new Error("Invalid credentials");
// Generate JWT
const token = jwt.sign(
{ userId: user._id, role: user.role },
process.env.JWT_SECRET,
{ expiresIn: "1d" }
);
return { token, user: { id: user._id, role: user.role } };
// middleware/auth.js
const checkPermission = (requiredPermission) => (req, res, next) => {
const userRole = req.user.role;
const role = await Role.findOne({ name: userRole });
if (!role.permissions.includes(requiredPermission)) {
return res.status(403).json({ error: "Access denied" });
}
next();
};
Usage in Routes:
// Only editors/admins can publish posts
router.post(
"/posts/:id/publish",
authMiddleware,
checkPermission("publish:post"),
publishPost
);
Endpoint: GET /api/analytics/posts/:postId
const analytics = await Analytics.aggregate([
{ $match: { postId: mongoose.Types.ObjectId(postId) } },
{ $group: { _id: "$date", totalViews: { $sum: "$views" } } },
{ $sort: { _id: 1 } } // Sort by date
]);
const trending = await Post.aggregate([
{ $sort: { views: -1 } },
{ $limit: 10 },
{ $project: { title: 1, views: 1 } }
]);
blog-api/
├── config/
│ ├── db.js # MongoDB connection
│ └── jwt.js # JWT settings
├── controllers/
│ ├── authController.js
│ ├── postController.js
│ └── analyticsController.js
├── models/
│ ├── User.js
│ ├── Post.js
│ └── Analytics.js
├── routes/
│ ├── authRoutes.js
│ ├── postRoutes.js
│ └── analyticsRoutes.js
├── middleware/
│ ├── auth.js # JWT verification
│ └── permissions.js # Role checks
└── app.js # Express setup
updatedAt
: postSchema.pre('save', function(next) {
this.updatedAt = new Date();
next();
});
postSchema.index({ title: "text", content: "text" });
You now have a scalable blog management system with:
✔ Secure user authentication
✔ Granular role-based permissions
✔ Detailed analytics tracking
✔ Optimized MongoDB schemas
What’s next?
👉 Try adding commenting functionality
👉 Implement SSE (Server-Sent Events) for real-time stats
Got questions? Ask below! 🚀
No comments yet. Be the first to comment!
Please log in to post a comment:
Sign in with Google