DocsHub
Back-EndSchemas

Blog Schema

Complete MongoDB schemas for a blog website — Post, Category, and Comment with full relationships.

Blog Schema

Schemas for a blog platform — posts reference an author and category, comments reference a post. Built to work alongside the User Schema.


Category Schema

// src/models/category.model.js
import mongoose from "mongoose";

const categorySchema = new mongoose.Schema(
  {
    name: {
      type: String,
      required: true,
      unique: true,
      trim: true,
    },
    slug: {
      type: String,
      required: true,
      unique: true,
      lowercase: true,
    },
    description: {
      type: String,
      default: "",
    },
  },
  { timestamps: true }
);

const Category = mongoose.model("Category", categorySchema);

export default Category;

Post Schema

// src/models/post.model.js
import mongoose from "mongoose";

const postSchema = new mongoose.Schema(
  {
    title: {
      type: String,
      required: [true, "Title is required"],
      trim: true,
      maxlength: 150,
    },
    slug: {
      type: String,
      required: true,
      unique: true,
      lowercase: true,
    },
    excerpt: {
      type: String,
      maxlength: 300,
    },
    content: {
      type: String,
      required: [true, "Content is required"],
    },
    coverImage: {
      type: String,
      default: "",
    },
    author: {
      type: mongoose.Schema.Types.ObjectId,
      ref: "User",
      required: true,
    },
    category: {
      type: mongoose.Schema.Types.ObjectId,
      ref: "Category",
    },
    tags: [{ type: String, lowercase: true, trim: true }],
    published: {
      type: Boolean,
      default: false,
    },
    publishedAt: {
      type: Date,
    },
    views: {
      type: Number,
      default: 0,
    },
    likes: [{ type: mongoose.Schema.Types.ObjectId, ref: "User" }],
  },
  { timestamps: true }
);

// auto-set publishedAt when a post is published for the first time
postSchema.pre("save", function (next) {
  if (this.isModified("published") && this.published && !this.publishedAt) {
    this.publishedAt = new Date();
  }
  next();
});

// index for fast text search on title and content
postSchema.index({ title: "text", content: "text" });

const Post = mongoose.model("Post", postSchema);

export default Post;

Comment Schema

// src/models/comment.model.js
import mongoose from "mongoose";

const commentSchema = new mongoose.Schema(
  {
    post: {
      type: mongoose.Schema.Types.ObjectId,
      ref: "Post",
      required: true,
    },
    author: {
      type: mongoose.Schema.Types.ObjectId,
      ref: "User",
      required: true,
    },
    content: {
      type: String,
      required: [true, "Comment cannot be empty"],
      maxlength: 1000,
    },
    // supports nested replies — a reply references its parent comment
    parentComment: {
      type: mongoose.Schema.Types.ObjectId,
      ref: "Comment",
      default: null,
    },
  },
  { timestamps: true }
);

const Comment = mongoose.model("Comment", commentSchema);

export default Comment;

How They Connect

// fetching a post with author and category populated
const post = await Post.findOne({ slug })
  .populate("author", "name avatar")
  .populate("category", "name slug");

// fetching comments for a post with author info
const comments = await Comment.find({ post: post._id, parentComment: null })
  .populate("author", "name avatar")
  .sort({ createdAt: -1 });

Summary

  • Post references User (author) and Categorytags is a plain array of strings for lightweight tagging
  • publishedAt is set automatically the first time a post is published, using a pre("save") hook
  • A text index on title and content enables $text search queries
  • Comment supports nested replies through a self-referencing parentComment field
  • Always use .populate() to bring in author and category details rather than storing duplicate data

On this page