Building a Scalable Blog Management System with MongoDB & Express.js

Author

Kritim Yantra

Aug 03, 2025

Building a Scalable Blog Management System with MongoDB & Express.js

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.


📂 Database Structure (MongoDB Collections)

1. users Collection

Stores 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)

2. roles Collection

Defines 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

3. posts Collection

Stores 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)

4. analytics Collection

Tracks 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)

🔐 Authentication System (Login/Register)

1. User Registration

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 });

2. User Login (JWT)

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 } };

🛡 Role-Based Access Control (Middleware)

// 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
);

📊 Analytics Endpoints

1. Get Post Views Over Time

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
]);

2. Top Trending Posts

const trending = await Post.aggregate([
  { $sort: { views: -1 } },
  { $limit: 10 },
  { $project: { title: 1, views: 1 } }
]);

🚀 Express.js API Structure

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

💡 Pro Tips for Optimization

  1. Use Mongoose Hooks for auto-updating updatedAt:
    postSchema.pre('save', function(next) {
      this.updatedAt = new Date();
      next();
    });
    
  2. Cache Popular Posts (Redis) to reduce DB load.
  3. Text Indexing for search:
    postSchema.index({ title: "text", content: "text" });
    

✅ Final Thoughts

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! 🚀

Comments

No comments yet. Be the first to comment!

Please log in to post a comment:

Sign in with Google

Related Posts

The Ultimate Express.js Project Structure Setup for Scalable Apps

Web Development

The Complete Joi Validation Guide for Express.js (2025)

Web Development

The Complete Joi Validation Guide for Express.js (2025)

#NodeJs #ExpressJs
Kritim Yantra

Kritim Yantra

Aug 02, 2025
Read

20 MongoDB Interview Questions and Answers for Beginners (2025 Edition)

Web Development