█
LastWrite
  • > Curriculum
  • > Pricing
  • > For Educators
  • > About
  • > Contact
Log InGet Started

Questions, concerns, bug reports, or suggestions? We read every message, write to us at [email protected].

More ways to reach us →
LastWrite

Structured computer science lessons for aspiring developers and security professionals.

[email protected]

(201) 785-7951

Mon–Fri, 9 AM–5 PM EST

Learn

  • Curriculum
  • Pricing

Company

  • About
  • For Educators & Schools
  • Contact Us

Legal

  • Terms of Service
  • Privacy Policy
© 2026 LastWrite. All rights reserved.
Curriculum/Programming Languages/MongoDB/Mongoose (ODM)
60 minIntermediate

Mongoose (ODM)

After this lesson, you will be able to: Use Mongoose: define schemas, validation, middleware (pre/post hooks), virtuals.

Mongoose is the standard Node.js ODM (object document mapper). Adds schemas, validation, hooks, virtuals to raw Mongo. Used by ~80% of production Node + Mongo apps.

Prerequisites:Schema Design

Schemas + models

Define structure + types.

python
// npm i mongoose
import mongoose, { Schema, model } from 'mongoose';
await mongoose.connect(process.env.MONGO_URI!);
const userSchema = new Schema({
email: { type: String, required: true, unique: true, lowercase: true, trim: true },
name: { type: String, required: true, minLength: 1, maxLength: 100 },
age: { type: Number, min: 0, max: 150 },
role: { type: String, enum: ['student', 'tutor', 'admin'], default: 'student' },
tags: [{ type: String }],
address: {
city: String,
zip: String,
},
createdAt: { type: Date, default: Date.now },
});
export const User = model('User', userSchema);
// CRUD
const u = await User.create({ email: '[email protected]', name: 'Alex', age: 30 });
const found = await User.findOne({ email: '[email protected]' });
await User.updateOne({ _id: u._id }, { $set: { age: 31 } });
await User.deleteOne({ _id: u._id });

Validation

Built-in + custom validators.

tsx
const emailSchema = new Schema({
email: {
type: String,
required: [true, 'Email is required'],
validate: {
validator: (v: string) => /^[^@]+@[^@]+\.[^@]+$/.test(v),
message: 'Invalid email format',
},
},
age: {
type: Number,
validate: {
validator: Number.isInteger,
message: 'Age must be an integer',
},
},
});
// Throws ValidationError on save if invalid
try {
await User.create({ email: 'not-an-email', age: 1.5 });
} catch (e) {
if (e.name === 'ValidationError') {
// handle
}
}

Middleware (hooks)

pre / post around save, find, etc.

python
import bcrypt from 'bcryptjs';
const userSchema = new Schema({
email: String,
password: String,
});
// Hash password before save
userSchema.pre('save', async function (next) {
if (!this.isModified('password')) return next();
this.password = await bcrypt.hash(this.password, 12);
next();
});
// Soft-delete pattern via query middleware
userSchema.pre(/^find/, function (next) {
// 'this' is the query, filter out deleted
this.where({ deletedAt: null });
next();
});

Virtuals + populate

Computed fields + reference resolution.

tsx
// Virtual field, not stored, computed on access
userSchema.virtual('fullName').get(function () {
return `${this.firstName} ${this.lastName}`;
});
// Reference + populate (Mongoose's $lookup wrapper)
const postSchema = new Schema({
title: String,
author: { type: Schema.Types.ObjectId, ref: 'User' },
});
const Post = model('Post', postSchema);
// Without populate, author is just an ObjectId
const post = await Post.findById(id);
// With populate, author becomes the full user doc
const withAuthor = await Post.findById(id).populate('author');
console.log(withAuthor.author.name);

💡 Mongoose vs raw driver

Mongoose adds schema enforcement, validation, hooks, populate, productivity wins. Raw driver is faster + smaller binary + finer control. Default: use Mongoose. Drop to raw driver for high-perf paths if you've measured the overhead.

Common mistakes

Skipping schema validation, Mongo becomes garbage in months. Pre-save password hash without `this.isModified` check, hashes the hash on every save. Populate everything by reflex (N+1 across collections). Using Mongoose's `_id` virtual as primary key in API responses, better to use _id directly or transform.

Sign in and purchase access to unlock this lesson.

Sign in to purchase
←Schema Design (Embed vs Reference)
Back to MongoDB
MongoDB Atlas→