Siksha Sarovar

Siksha Sarovar (sikshasarovar.com) is a free educational web application that helps students in India learn programming and prepare for academic and competitive exams. The platform offers structured coding courses (C, C++, Python, Java, HTML, CSS, PHP, Power BI, AI, Machine Learning, Data Science), complete university curriculum notes for BCA/MCA students with previous year question papers, Class 10 and Class 12 CBSE/HBSE school notes, and dedicated preparation material for SSC, UPSC, Banking, Railway and other government exams. Browsing the site is completely free and requires no account. Users may optionally sign in with Google solely to save their learning progress, quiz scores and personal preferences across devices.

Privacy Policy | Terms of Service | Contact Siksha Sarovar | About Siksha Sarovar

v4.0.9 · PWA
Siksha Sarovar logo
Siksha Sarovar
Your Learning Universe

Siksha Sarovar is a free e-learning platform for coding courses, BCA university notes and competitive exam preparation. Optional Google sign-in saves your learning progress across devices.

Initializing knowledge base…
Compiling modules 0%

4. Data Modelling for Backend with Mongoose

Lesson 4 of 23 in the free Backend Development notes on Siksha Sarovar, written by Rohit Jangra.

What is Data Modelling?

Data modelling is the process of defining how your application data is structured, stored, and related in the database. A well-designed data model directly affects your application's performance, scalability, and ease of development. Poor data modelling leads to slow queries, data inconsistencies, and complex code that is hard to maintain.

In MongoDB, data modelling involves deciding:

  1. What schemas (document shapes) to define for each entity
  2. How entities relate to each other (embedding vs referencing)
  3. What indexes to add for fast queries
  4. What validation rules to enforce at the database level

---

Normalisation vs Denormalisation

ConceptNormalisationDenormalisation
DefinitionSplit data into separate collections, use referencesStore related data together in one document
StorageLess duplicationMore duplication
Query speedNeeds $lookup (joins) — slowerFast single-document reads
Update complexityUpdate one place, consistentMust update multiple places
Use caseFrequently updated dataFrequently read, rarely updated data
MongoDB examplePost with author: ObjectIdPost with author: { name, avatar } embedded
MongoDB favours denormalisation for data that is read together frequently. Reference (normalise) data that changes often or is shared across many documents.

---

Mongoose Schema and Model

Schema defines the shape of documents in a collection — fields, types, validation, defaults. Model is a JavaScript class created from a Schema. It provides methods to query and manipulate documents: Model.find(), Model.create(), Model.findByIdAndUpdate(), etc.

import mongoose from 'mongoose';
const { Schema, model } = mongoose;

const userSchema = new Schema({ name: String }, { timestamps: true });
const User = model('User', userSchema);
// Collection name: 'users' (Mongoose pluralises and lowercases automatically)

---

Field Types in Mongoose

TypeUsageExample
StringText dataname: String
NumberIntegers and floatsprice: Number
Booleantrue/falseisPublished: Boolean
DateDate/time valuescreatedAt: Date
mongoose.Schema.Types.ObjectIdReferences to other documentsowner: ObjectId
ArrayList of values or subdocumentstags: [String]
MixedAny type (use sparingly)metadata: Schema.Types.Mixed
BufferBinary datafile: Buffer
MapKey-value pairspreferences: Map

---

Validators in Mongoose

const productSchema = new Schema({
  name: {
    type: String,
    required: [true, 'Product name is required'],
    trim: true,
    minlength: [3, 'Name must be at least 3 characters'],
    maxlength: [100, 'Name cannot exceed 100 characters'],
  },
  price: {
    type: Number,
    required: true,
    min: [0, 'Price cannot be negative'],
  },
  category: {
    type: String,
    enum: {
      values: ['electronics', 'clothing', 'food', 'books'],
      message: '{VALUE} is not a valid category',
    },
  },
  email: {
    type: String,
    match: [/^[\w.-]+@[\w.-]+\.\w+$/, 'Invalid email format'],
  },
  rating: {
    type: Number,
    default: 0,
    min: 0,
    max: 5,
  },
});

---

Relationships: Embedded vs Referenced

Embedded Documents (Denormalised):

// Address embedded inside User — good for one-to-one stable data
const userSchema = new Schema({
  name: String,
  address: {
    street: String,
    city: String,
    state: String,
    pincode: String,
  },
});

Referenced Documents (Normalised):

// Post references User — good for one-to-many with updates
const postSchema = new Schema({
  title: String,
  content: String,
  author: { type: Schema.Types.ObjectId, ref: 'User' },
});

// Populate to get full author data:
const post = await Post.findById(id).populate('author', 'name email avatar');
CriterionEmbedReference
Data changes rarely✅ GoodOK
Data is always accessed together✅ IdealNeeds populate()
Many-to-many relationships❌ Avoid✅ Use references
Sub-document count can be unlimited❌ 16MB doc limit✅ Safe
Query single entity frequently✅ FastNeeds join

---

Indexing

Indexes dramatically speed up queries but slow down writes (index must be updated on insert/update). Add indexes on fields you query or sort by frequently.

// Single field index
userSchema.index({ email: 1 });         // Ascending
userSchema.index({ username: 1 }, { unique: true });

// Compound index
postSchema.index({ author: 1, createdAt: -1 }); // Posts by author, newest first

// Text index for search
productSchema.index({ name: 'text', description: 'text' });

---

Virtuals and Timestamps

Virtuals are computed properties not stored in MongoDB:

userSchema.virtual('fullName').get(function () {
  return `${this.firstName} ${this.lastName}`;
});

Timestamps automatically add createdAt and updatedAt fields:

const schema = new Schema({ ... }, { timestamps: true });

---

Pre/Post Hooks

Hooks (middleware) run before or after Mongoose operations:

// Hash password before saving
userSchema.pre('save', async function (next) {
  if (!this.isModified('password')) return next();
  this.password = await bcrypt.hash(this.password, 10);
  next();
});

// Log after deletion
userSchema.post('findOneAndDelete', function (doc) {
  console.log(`User ${doc.email} was deleted`);
});