Skip to content

Mean 05 Mongoose Schemas

DodaTech 5 min read

title: "Mongoose Schemas — Defining Data Models for MEAN Stack" description: "Learn Mongoose schemas for the MEAN Stack: define fields, data types, validation, default values, and indexes for MongoDB document structure." weight: 15 date: 2026-06-28 lastmod: 2026-06-28 tags: [frameworks, mean]

Mongoose schemas define the structure of documents in MongoDB, specifying fields, data types, validation rules, default values, and indexes.

What You'll Learn

You will understand how to define Mongoose schemas with various field types, validation, timestamps, and indexes.

Why It Matters

Schemas enforce data structure and validation at the application level, preventing invalid data from being saved to the database.

Real-World Use

Durga Antivirus Pro uses Mongoose schemas for threat reports with validation on severity levels, required fields, and automatic timestamps.

flowchart LR
    A[Mongoose Schema] --> B[Field Definitions]
    A --> C[Validation Rules]
    A --> D[Timestamps]
    A --> E[Indexes]
    B --> F[MongoDB Document]
    C --> F
    D --> F
    E --> F
    style A fill:#4a90d9,color:#fff

Basic Schema Definition

Create a Mongoose schema for a User model.

// backend/models/User.js
const mongoose = require('mongoose');

const userSchema = new mongoose.Schema({
  name: {
    type: String,
    required: [true, 'Name is required'],
    trim: true,
    minlength: [2, 'Name must be at least 2 characters'],
    maxlength: [100, 'Name cannot exceed 100 characters']
  },
  email: {
    type: String,
    required: [true, 'Email is required'],
    unique: true,
    lowercase: true,
    match: [/^\S+@\S+\.\S+$/, 'Please provide a valid email']
  },
  age: {
    type: Number,
    min: [0, 'Age cannot be negative'],
    max: [150, 'Age seems unrealistic']
  },
  role: {
    type: String,
    enum: ['user', 'admin', 'moderator'],
    default: 'user'
  }
}, {
  timestamps: true
});

Expected output: A schema for users with name, email, age, and role fields. Each field has validation rules. Timestamps add createdAt and updatedAt automatically.

Field Types

Mongoose supports multiple field types for different data.

const productSchema = new mongoose.Schema({
  name: String,
  price: Number,
  inStock: Boolean,
  tags: [String],           // Array of strings
  dimensions: {             // Nested object
    width: Number,
    height: Number,
    depth: Number
  },
  metadata: mongoose.Schema.Types.Mixed,  // Any type
  publishedAt: Date,
  specifications: [{        // Array of nested objects
    key: String,
    value: String
  }]
});

Expected output: A product schema with various field types including strings, numbers, booleans, arrays, nested objects, dates, and mixed types.

Custom Validation

Add custom validation logic for complex requirements.

const orderSchema = new mongoose.Schema({
  items: [{
    productId: { type: mongoose.Schema.Types.ObjectId, ref: 'Product' },
    quantity: { type: Number, required: true, min: 1 },
    price: { type: Number, required: true }
  }],
  status: {
    type: String,
    enum: ['pending', 'confirmed', 'shipped', 'delivered', 'cancelled'],
    default: 'pending'
  },
  totalAmount: Number
});

// Custom validation
orderSchema.pre('save', function(next) {
  this.totalAmount = this.items.reduce((sum, item) => {
    return sum + (item.price * item.quantity);
  }, 0);
  next();
});

Expected output: An order schema with embedded items array and a pre-save hook that calculates the total amount automatically before saving.

Schema Indexes

Add indexes for query performance.

const postSchema = new mongoose.Schema({
  title: { type: String, required: true },
  slug: { type: String, required: true, unique: true },
  author: { type: mongoose.Schema.Types.ObjectId, ref: 'User', index: true },
  tags: [{ type: String }],
  status: { type: String, enum: ['draft', 'published'], index: true },
  publishedAt: Date,
  viewCount: { type: Number, default: 0, index: true }
});

// Compound index
postSchema.index({ status: 1, publishedAt: -1 });
postSchema.index({ tags: 1 });

Expected output: Indexes on frequently queried fields. Unique index on slug. Compound index on status + publishedAt for efficient queries.

Common Mistakes

  1. Not defining required validations: Without required: true, fields can be missing from documents, causing undefined errors later.

  2. Forgetting unique indexes on email fields: Without unique: true, multiple users can register with the same email.

  3. Using overly permissive types: Using String for everything loses type safety. Use Number, Boolean, Date for appropriate fields.

  4. Not trimming string fields: User input often has extra whitespace. Use trim: true to clean strings automatically.

  5. Ignoring the timestamps option: Manually managing createdAt and updatedAt is error-prone. Use timestamps: true.

Practice Questions

  1. What does timestamps: true do in a Mongoose schema?

It automatically adds createdAt and updatedAt fields and updates them on document creation and modification.

  1. How do you define a unique field in a schema?

Set unique: true on the field. This creates a unique index in MongoDB that prevents duplicate values.

  1. What is the difference between required validation and unique index?

required prevents saving documents without the field. unique prevents duplicate values in the field across documents.

  1. How do you create a nested object in a schema?

Define an object with nested field definitions inside the parent field's object.

  1. What is a pre-save hook?

A middleware function that runs before saving a document. Used for computed fields, password hashing, or validation.

Challenge

Create schemas for a blog application: User (name, email, password), Post (title, slug, content, author, tags, status, publishedAt), and Comment (post reference, author, content, createdAt). Add appropriate validation, indexes, and timestamps.

Frequently Asked Questions

Can I modify a schema after data exists?

Yes. Mongoose is flexible. Add new fields with default values. Renaming or removing fields requires data migration.

What is the difference between required and validate?

required checks if the value exists. validate runs custom logic to check if the value meets specific criteria.

How do I reference another collection in a schema?

Use mongoose.Schema.Types.ObjectId with ref option. This creates a reference for population (JOIN-like queries).

What is the Mixed type used for?

Mixed accepts any data type. Use it sparingly as it bypasses schema validation and type checking.

How many indexes are too many?

Indexes speed up reads but slow down writes. Add indexes for query patterns you actually use, not for every field.

Mini Project

Create Mongoose schemas for an e-commerce application: Product (name, price, category, stock, description), Category (name, slug, description), and Review (product, user, rating, comment, createdAt).

What's Next

Learn to compile schemas into Mongoose Models and perform database operations.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro