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