Back

MongoDB Notes · Lesson MG.5

Mongoose — Node.js Se MongoDB

CODE SCOPE MONGODB SERIESLESSON MG.5 OF MG.1–MG.11

The bridge real apps use: connect Node.js to MongoDB, give your data a schema and a model, and let validation guard the door.

Free PDF of this lesson — save it, print it, revise offline. This lesson's own video is not on the channel yet — the Watch button opens the Code Scope channel, where the rest of the series lives.

Introduction

mongosh is a fine workshop tool, but your app cannot ask a human to type shell commands. A Node.js app talks to MongoDB through Mongoose — the library that connects, and then does something mongosh never did: it gives your flexible documents a schema, a promised shape that Mongoose enforces on every save. Remember MG.3's typo filter, silently matching nothing? And MG.4's missing-field surprises? A schema is the guard who fights exactly that class of bug. This lesson: connect, define the schema, build the model, and watch validation refuse a bad document at the door.

(Roman Urdu: Mongoose = Node app aur MongoDB ke darmiyan pul — aur darwaze par khara chowkidar, schema.)

Key points

  • Connect once, at the start: mongoose.connect(process.env.MONGO_URI) — the Atlas connection string from MG.2, read from .env (never typed into code). A successful connect logs and stays open; every model then rides that one connection.
  • A schema is a promise about shape: new mongoose.Schema({ name: { type: String, required: true }, marks: { type: Number, min: 0, max: 100 } }) — field names, types, and rules. Flexible MongoDB underneath; disciplined Mongoose on top. You choose the strictness per field.
  • A model is the working tool: mongoose.model('Student', studentSchema) returns a class-like tool — Student.create(), Student.find(), Student.findOneAndUpdate(), Student.deleteOne() — the CRUD of MG.3, wearing JavaScript clothes, aimed at the students collection.
  • Validation runs on every save: a missing required field, a marks of 140 against max: 100, text where a Number belongs — Mongoose rejects the save with a ValidationError naming the field and the broken rule. The bad document never reaches the database. This is the schema earning its salary.
  • The model adds two fields of its own: every saved document carries _id (the ObjectId, as always) and __v (a version counter Mongoose maintains). Seeing __v: 0 on a fresh document is normal — it is Mongoose's signature.
  • Async is the weather: database calls take time, so Mongoose methods return promises — real code uses await inside an async function (the JavaScript series' 3.22–3.23 lessons are the foundation here).

Code examples

Example 1 — Connect: one line, from .env

import mongoose from 'mongoose';

await mongoose.connect(process.env.MONGO_URI);
console.log('MongoDB connected:', mongoose.connection.name);

What it does: The connection string (with its password) is read from the environment — the .env habit from MG.2 — and Mongoose opens one connection to Atlas. mongoose.connection.name is the database the string pointed at.

What you will see:

MongoDB connected: schoolDB

One quiet line, and the app now holds a live road into the database. Everything after this in the app — every model, every query — travels on this connection. If the string were wrong, this is where the app would fail loudly at startup instead of mysteriously at the first query: another reason to connect first and connect once.

Example 2 — Schema, model, and the first saved student

const studentSchema = new mongoose.Schema({
  name: { type: String, required: true },
  class: { type: String, required: true },
  marks: { type: Number, min: 0, max: 100 },
  city: String
});

const Student = mongoose.model('Student', studentSchema);

const ali = await Student.create({
  name: 'Ali Raza', class: '10-A', marks: 88, city: 'Sahiwal'
});
console.log(ali);

What it does: The schema promises: name and class are required text; marks is a number between 0 and 100; city is optional text. The model is built from it, and create saves Ali through the schema. Watch the reply — the document wears two extra fields now.

What you will see:

{
  name: 'Ali Raza',
  class: '10-A',
  marks: 88,
  city: 'Sahiwal',
  _id: new ObjectId('650000000000000000000001'),
  __v: 0
}

The same Ali as mongosh made — same ObjectId style, same facts — plus __v: 0, Mongoose's version stamp. The shape was checked on the way in: had marks been 'eighty' or 880, this save would never have happened. Which is the next example.

Example 3 — The guard works: validation refuses a bad save

try {
  await Student.create({ class: '10-A', marks: 88, city: 'Sahiwal' });
} catch (err) {
  console.log(err.name + ':', err.message);
}
const sahiwal = await Student.find({ city: 'Sahiwal' });
console.log(sahiwal.length);

What it does: The create is missing name — a required field. In mongosh this document would have slid in silently and haunted later queries; here the schema stops it, and the catch prints exactly why. The find after it proves the database holds only honest documents — the two real Sahiwal students (Ali, and Hira from the class data), not the broken one.

What you will see:

ValidationError: Student validation failed: name: Path `name` is required.
2

Read that error like a teacher wrote it: which rule (validation), which field (name), what is wrong (required, missing). And the count 2 is the quiet victory — the bad document exists nowhere. This is the whole argument for Mongoose in one example: the same flexibility as raw MongoDB, with a guard at the door.

Quick recap

  • Mongoose connects Node.js to MongoDB and adds the discipline MongoDB itself leaves to you.
  • mongoose.connect(process.env.MONGO_URI) — once, from .env, at startup.
  • Schema = the promised shape (types + required + min/max rules); model = the tool built from it (create, find, findOneAndUpdate, deleteOne — MG.3's CRUD in JavaScript).
  • ValidationError is the guard speaking: field named, rule named, document refused. Saved documents carry _id and Mongoose's __v.

Common mistakes

  • Typing the connection string into code: the string holds a password. Committed to GitHub, it is public within the hour — .env + .gitignore (Git series G.3) is not decoration.
  • Forgetting await: const list = Student.find() without await holds a promise of documents, not documents — printing it shows a query object, and the bug looks like MongoDB's fault. Database calls are async; await them.(Roman Urdu: await bhoolna sab se aam Mongoose ghalti hai — jawab nahi, jawab ka wada milta hai.)
  • Model name confusion: mongoose.model('Student', ...) writes to the students collection (lowercase, plural). Beginners create a second schema named Students, get a second empty collection, and swear the data vanished. One model name per collection.
  • Making everything required (or nothing): a schema stricter than real life rejects honest data; a schema with no rules is mongosh with extra steps. Required = the fields a record is meaningless without (name, class); the rest earn rules from real mistakes.

Practice

  1. Write the schema for a Product model for a dukaan: name (required text), price (required number, min 1), stock (number, default 0), category (text). Which fields did you make required — and what real-world mess does each rule prevent?
  2. Predict, then check: what does Student.create({ name: 'Test', class: '9-A', marks: 140 }) do against Example 2's schema? Write the error's first line from memory of the pattern.
  3. Convert one MG.3 shell command of your choice into model code: the count-then-delete safety pair, using countDocuments and deleteOne on the model — keeping the same order (count first).