R
Rishtaara
MongoDB Fundamentals
Lesson 37 of 40Article20 min

Node.js — Connect with Mongoose

Mongoose is the most popular ODM (Object Document Mapper) for MongoDB and Node.js. It adds schemas, validation, middleware hooks, population (like joins), and a fluent query API on top of the official MongoDB driver.

Why Mongoose for Node.js apps

Mongoose is the most popular ODM (Object Document Mapper) for MongoDB and Node.js. It adds schemas, validation, middleware hooks, population (like joins), and a fluent query API on top of the official MongoDB driver.

Real-life example: The raw MongoDB driver is a manual car — full control, more work. Mongoose is an automatic with cruise control — sensible defaults, guardrails, and less boilerplate for everyday CRUD.

  • Schema — defines shape, types, defaults, and validation rules.
  • Model — constructor compiled from schema; maps to a collection.
  • Document — instance of a model representing one MongoDB document.
  • Middleware — pre/post hooks on save, validate, remove, and aggregate.

Connect MongoDB with Mongoose

Project setup and connection
// npm install mongoose
import mongoose from "mongoose";

const MONGODB_URI = process.env.MONGODB_URI ?? "mongodb://127.0.0.1:27017/rishtaara";

export async function connectDB() {
  if (mongoose.connection.readyState >= 1) return;

  await mongoose.connect(MONGODB_URI, {
    serverSelectionTimeoutMS: 5000,
  });

  console.log("MongoDB connected:", mongoose.connection.name);
}

// Graceful shutdown
process.on("SIGINT", async () => {
  await mongoose.disconnect();
  process.exit(0);
});

Schema, model, and CRUD

Define schema and model
const courseSchema = new mongoose.Schema(
  {
    title: { type: String, required: true, trim: true },
    slug: { type: String, required: true, unique: true, lowercase: true },
    level: { type: String, enum: ["beginner", "intermediate", "advanced"] },
    lessons: { type: Number, default: 0 },
    tags: [String],
    instructor: {
      name: String,
      bio: String,
    },
    published: { type: Boolean, default: false },
  },
  { timestamps: true }
);

courseSchema.index({ slug: 1 });
courseSchema.index({ published: 1, level: 1 });

export const Course = mongoose.model("Course", courseSchema);
CRUD operations
// Create
const course = await Course.create({
  title: "MongoDB Fundamentals",
  slug: "mongodb-fundamentals",
  level: "beginner",
  tags: ["database", "nosql"],
  instructor: { name: "Rishtaara Team" },
});

// Read — with filter, projection, sort, pagination
const published = await Course.find({ published: true, level: "beginner" })
  .select("title slug lessons")
  .sort({ createdAt: -1 })
  .skip(0)
  .limit(10);

const one = await Course.findOne({ slug: "mongodb-fundamentals" });

// Update
await Course.updateOne(
  { slug: "mongodb-fundamentals" },
  { $inc: { lessons: 1 }, $set: { published: true } }
);

// Delete
await Course.deleteOne({ slug: "draft-course" });

.lean() and aggregate tips

.lean() tells Mongoose to return plain JavaScript objects instead of full Mongoose documents. Skips change tracking and hydration — significantly faster for read-only API responses.

Use Model.aggregate() for analytics pipelines. Mongoose aggregate returns plain objects by default (no .lean() needed).

Real-life example: .lean() is photocopying a report instead of borrowing the original binder — faster to hand out, but you cannot edit the master copy through the photocopy.

Tip: Add .explain() to Mongoose queries in development: Course.find({ published: true }).explain('executionStats') — same as mongosh explain.
lean() for API performance
// Read-only list endpoint — use lean()
app.get("/api/courses", async (req, res) => {
  const courses = await Course.find({ published: true })
    .select("title slug level lessons")
    .sort({ createdAt: -1 })
    .limit(20)
    .lean();

  res.json(courses);
});

// When you need save() and middleware — do NOT use lean()
const doc = await Course.findOne({ slug: "mongodb-fundamentals" });
doc.lessons += 1;
await doc.save();
Aggregation with Mongoose
const stats = await Course.aggregate([
  { $match: { published: true } },
  {
    $group: {
      _id: "$level",
      count: { $sum: 1 },
      avgLessons: { $avg: "$lessons" },
    },
  },
  { $sort: { count: -1 } },
]);

// Populate — reference join (use sparingly on hot paths)
const userSchema = new mongoose.Schema({
  name: String,
  courseIds: [{ type: mongoose.Schema.Types.ObjectId, ref: "Course" }],
});
const users = await User.find().populate("courseIds", "title slug");