Blog App (MongoDB with Mongoose)

This document outlines the MongoDB database schema for a sample blog application. The schema is designed to be efficient, scalable, and easy to understand. We use Mongoose for defining our data models with TypeScript.

The core of our application revolves around four main collections: User, Post, Category, and Comment.

Installation Guide

To add these schemas to your project, run:

npx servercn-cli add schema blog-app

1. User Schema

The User schema stores essential information about blog authors and administrators, including authentication details and profile information.

MVC Path: src/models/user.model.ts

Feature Path: src/modules/user/user.model.ts

import mongoose, { Document, Model, Schema } from "mongoose";
 
interface IFile {
  public_id: string;
  url: string;
  size: number;
}
 
export interface IUser extends Document {
  _id: mongoose.Types.ObjectId;
  name: string;
  email: string;
  password?: string;
  role: "user" | "admin";
  isEmailVerified: boolean;
  lastLoginAt?: Date;
  failedLoginAttempts: number;
  lockUntil?: Date;
  avatar?: IFile;
 
  provider: "local" | "google" | "github";
  providerId?: string;
 
  isDeleted: boolean;
  deletedAt?: Date | null;
  reActivateAvailableAt?: Date | null;
 
  createdAt: Date;
  updatedAt: Date;
}
 
const userSchema = new Schema<IUser>(
  {
    name: {
      type: String,
      required: [true, "Name is required"],
      trim: true
    },
    email: {
      type: String,
      required: [true, "Email is required"],
      unique: true,
      lowercase: true,
      trim: true
    },
    password: {
      type: String,
      select: false,
      default: null
    },
    provider: {
      type: String,
      enum: ["local", "google", "github"],
      default: "local"
    },
    providerId: {
      type: String,
      default: null
    },
    role: {
      type: String,
      enum: ["user", "admin"],
      default: "user"
    },
    avatar: {
      public_id: String,
      url: String,
      size: Number
    },
    isEmailVerified: {
      type: Boolean,
      default: false
    },
    lastLoginAt: {
      type: Date
    },
    failedLoginAttempts: {
      type: Number,
      required: true,
      default: 0
    },
    lockUntil: {
      type: Date
    },
    isDeleted: {
      type: Boolean,
      default: false
    },
    deletedAt: {
      type: Date,
      default: null
    },
    reActivateAvailableAt: {
      type: Date,
      default: null
    }
  },
  {
    timestamps: true
  }
);
 
// Performance Indexes
userSchema.index({ provider: 1, providerId: 1 });
userSchema.index({ role: 1 });
userSchema.index({ isDeleted: 1 });
 
const User: Model<IUser> =
  mongoose.models.User || mongoose.model<IUser>("User", userSchema);
 
export default User;

Installation

npx servercn-cli add schema blog-app/user

2. Post Schema

The Post schema stores blog posts with support for titles, content, excerpts, featured images, and publication status. Includes text search capabilities and like tracking.

MVC Path: src/models/post.model.ts

Feature Path: src/modules/post/post.model.ts

import mongoose, { Document, Model, Schema, Types } from "mongoose";
import { PostStatus } from "./post.types";
import { POST_STATUSES } from "./post.constants";
 
interface IFile {
  public_id: string;
  url: string;
  size: number;
}
 
export interface IPost extends Document {
  _id: Types.ObjectId;
 
  title: string;
  slug: string;
  content: string;
 
  excerpt?: string;
  author: Types.ObjectId;
  category?: Types.ObjectId;
  tags?: string[];
  featuredImage: IFile | null;
 
  views: number;
  likes: Types.ObjectId[];
  likeCount: number;
 
  status: PostStatus;
  publishedAt?: Date;
 
  createdAt: Date;
  updatedAt: Date;
}
 
const postSchema = new Schema<IPost>(
  {
    title: {
      type: String,
      required: true
    },
 
    slug: {
      type: String,
      required: true
    },
 
    content: {
      type: String,
      required: true
    },
 
    excerpt: {
      type: String
    },
 
    author: {
      type: Schema.Types.ObjectId,
      ref: "User",
      required: true
    },
 
    category: {
      type: Schema.Types.ObjectId,
      ref: "Category"
    },
 
    tags: {
      type: [String],
      default: []
    },
 
    featuredImage: {
      public_id: String,
      url: String,
      size: Number
    },
 
    views: {
      type: Number,
      default: 0
    },
 
    likes: [
      {
        type: Schema.Types.ObjectId,
        ref: "User"
      }
    ],
 
    likeCount: {
      type: Number,
      default: 0
    },
 
    status: {
      type: String,
      enum: POST_STATUSES,
      default: "draft"
    },
 
    publishedAt: {
      type: Date
    }
  },
  {
    timestamps: true
  }
);
 
// Text search index
postSchema.index({ title: "text", content: "text" });
 
// Unique compound index
postSchema.index({ author: 1, slug: 1 }, { unique: true });
 
// Query optimization indexes
postSchema.index({ category: 1 });
postSchema.index({ createdAt: -1 });
 
const Post: Model<IPost> =
  mongoose.models.Post || mongoose.model<IPost>("Post", postSchema);
 
export default Post;

Installation

npx servercn-cli add schema blog-app/post

3. Category Schema

The Category schema organizes blog posts into hierarchical categories with names, slugs, and descriptions.

MVC Path: src/models/category.model.ts

Feature Path: src/modules/category/category.model.ts

import mongoose, { model, Schema } from "mongoose";
 
export interface ICategory extends Document {
  _id: mongoose.Types.ObjectId;
  name: string;
  slug: string;
  description?: string;
  createdAt: Date;
  updatedAt: Date;
}
 
const categorySchema = new Schema<ICategory>(
  {
    name: {
      type: String,
      required: true
    },
 
    slug: {
      type: String,
      required: true,
      unique: true
    },
 
    description: {
      type: String
    }
  },
  { timestamps: true }
);
 
categorySchema.index({ slug: 1, name: 1 }, { unique: true });
 
const Category = model<ICategory>("Category", categorySchema);
 
export default Category;

Installation

npx servercn-cli add schema blog-app/category

4. Comment Schema

The Comment schema enables user engagement through comments on blog posts, with support for nested replies and likes.

MVC Path: src/models/comment.model.ts

Feature Path: src/modules/comment/comment.model.ts

import { model, Schema, Types } from "mongoose";
 
export interface IComment extends Document {
  _id: Types.ObjectId;
 
  post: Types.ObjectId;
  author: Types.ObjectId;
  content: string;
  parentComment?: Types.ObjectId;
 
  likes: Types.ObjectId[];
 
  createdAt: Date;
  updatedAt: Date;
}
 
const commentSchema = new Schema<IComment>(
  {
    post: {
      type: Schema.Types.ObjectId,
      ref: "Post",
      required: true
    },
 
    author: {
      type: Schema.Types.ObjectId,
      ref: "User",
      required: true
    },
 
    content: {
      type: String,
      required: true,
      trim: true,
      maxlength: 500,
      minlength: 1
    },
 
    likes: [
      {
        type: Schema.Types.ObjectId,
        ref: "User"
      }
    ],
 
    parentComment: {
      type: Schema.Types.ObjectId,
      ref: "Comment"
    }
  },
  { timestamps: true }
);
 
// Indexes for query performance
commentSchema.index({ post: 1 });
commentSchema.index({ author: 1 });
commentSchema.index({ parentComment: 1 });
 
const Comment = model<IComment>("Comment", commentSchema);
 
export default Comment;

Installation

npx servercn-cli add schema blog-app/comment

Key Features

Posts support three states:

  • Draft: Initial state, not visible to public
  • Published: Visible to all users
  • Archived: Hidden from main listings but retained

The Post collection includes a text index on title and content fields for full-text search capabilities.

Comments support threaded discussions through the parentComment field, enabling unlimited nesting levels.

Both posts and comments track likes using arrays of user ObjectIds, with cached counts for performance.

User schema includes fields for soft delete functionality:

  • isDeleted: Flag to mark deleted users
  • deletedAt: Timestamp of deletion
  • reActivateAvailableAt: When reactivation becomes available
  • Password field excluded by default with select: false
  • Account lockout support with failedLoginAttempts and lockUntil
  • Email verification tracking with isEmailVerified

File & Folder Structure

ServerCN

Select a file to view its contents

Installation

npx servercn-cli add sc blog-app