DocsHub
Mongoose

Models

Learn how to create Mongoose models from schemas and use them to perform full CRUD operations on your data.

Models

A model is what you actually use to interact with your database. It is built from a schema and gives you methods to create, read, update, and delete documents — all with the structure and validation defined in the schema.

If a schema is the blueprint, a model is the constructor that builds real documents from that blueprint.


Creating a Model

const mongoose = require('mongoose');

const studentSchema = new mongoose.Schema({
  name: String,
  age: Number,
  grade: String,
  enrolled: Boolean
});

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

mongoose.model('Student', studentSchema) does two things:

  1. Registers the schema under the name "Student"
  2. Returns a model — a class you use to create and query documents

Collection naming

Mongoose automatically converts the model name to lowercase and pluralizes it to determine the collection name:

Model nameCollection name
Studentstudents
Teacherteachers
Coursecourses
Enrollmentenrollments

You do not need to create the collection manually — MongoDB creates it automatically the first time you insert a document.


Create — Inserting Documents

Model.create()

The most common way to insert a document:

const student = await Student.create({
  name: "Ali Hassan",
  age: 16,
  grade: "10th",
  enrolled: true
});

console.log(student._id);   // auto-generated ObjectId
console.log(student.name);  // "Ali Hassan"

create() returns the created document — including its generated _id and any default values from the schema.

Insert multiple documents

Pass an array to create():

const students = await Student.create([
  { name: "Sara Ahmed", age: 15, grade: "9th" },
  { name: "Umar Farooq", age: 17, grade: "11th" },
  { name: "Ayesha Khan", age: 15, grade: "9th" }
]);

console.log(students.length); // 3

new Model() + save()

You can also create a document instance first, then save it separately:

const student = new Student({
  name: "Bilal Ahmed",
  age: 16,
  grade: "10th"
});

console.log(student._id); // ObjectId is generated immediately, even before saving

await student.save();     // now it is written to the database

This pattern is useful when you need to modify the document before saving it:

const student = new Student({ name: "Bilal Ahmed", age: 16, grade: "10th" });

// Do something before saving
if (student.age >= 16) {
  student.subjects = ["Math", "Physics", "Computer Science"];
}

await student.save();

Read — Querying Documents

find()

Returns an array of all matching documents:

// All students
const allStudents = await Student.find();

// All 10th grade students
const tenthGrade = await Student.find({ grade: "10th" });

// All enrolled students in 10th grade
const enrolledTenth = await Student.find({ grade: "10th", enrolled: true });

findOne()

Returns the first matching document, or null if none found:

const student = await Student.findOne({ name: "Ali Hassan" });

if (student) {
  console.log(`Found: ${student.name}, Grade: ${student.grade}`);
} else {
  console.log("Student not found");
}

findById()

A shortcut for finding by _id. Mongoose automatically converts the string to an ObjectId for you:

const student = await Student.findById("64a1f2c3e4b0a1b2c3d4e5f6");

This is one of the most useful Mongoose conveniences — with the raw driver you would need to write new ObjectId("64a1...") yourself. Mongoose does it automatically.


Update — Modifying Documents

Mongoose provides several update methods. The key thing to understand is that some return the updated document and some only return a status report.

updateOne()

Updates the first matching document. Returns a status object — not the document itself:

const result = await Student.updateOne(
  { name: "Ali Hassan" },
  { $set: { age: 17 } }
);

console.log(result.matchedCount);  // 1
console.log(result.modifiedCount); // 1

updateMany()

Updates all matching documents:

const result = await Student.updateMany(
  { grade: "10th" },
  { $set: { enrolled: true } }
);

console.log(result.modifiedCount); // number of documents updated

findByIdAndUpdate()

Finds a document by _id, updates it, and returns the document — not just a status report:

const updatedStudent = await Student.findByIdAndUpdate(
  "64a1f2c3e4b0a1b2c3d4e5f6",
  { $set: { grade: "11th" } },
  { new: true }  // return the updated document, not the original
);

console.log(updatedStudent.grade); // "11th"

By default, findByIdAndUpdate() and findOneAndUpdate() return the original document — before the update was applied. Pass { new: true } to get the updated version instead. This trips up almost every Mongoose beginner.

findOneAndUpdate()

Like findByIdAndUpdate() but with any filter, not just _id:

const updated = await Student.findOneAndUpdate(
  { name: "Ali Hassan" },
  { $set: { hasPaidFees: true } },
  { new: true }
);

Modify and save()

You can also fetch a document, modify its fields directly, and call save():

const student = await Student.findOne({ name: "Ali Hassan" });

student.age = 17;
student.grade = "11th";
student.subjects.push("Computer Science");

await student.save();

This approach runs full schema validation and any pre('save') middleware — which updateOne() does not. We cover middleware in detail later in this section.


Delete — Removing Documents

deleteOne()

const result = await Student.deleteOne({ name: "Bilal Ahmed" });
console.log(result.deletedCount); // 1

deleteMany()

const result = await Student.deleteMany({ enrolled: false });
console.log(result.deletedCount); // number deleted

findByIdAndDelete()

Finds by _id, deletes it, and returns the deleted document:

const deleted = await Student.findByIdAndDelete("64a1f2c3e4b0a1b2c3d4e5f6");
console.log(`Deleted: ${deleted.name}`);

findOneAndDelete()

Like findByIdAndDelete() but with any filter:

const deleted = await Student.findOneAndDelete({ name: "Bilal Ahmed" });

Counting Documents

// Count all students
const total = await Student.countDocuments();

// Count enrolled students
const enrolledCount = await Student.countDocuments({ enrolled: true });

// Count students in a specific grade
const tenthGradeCount = await Student.countDocuments({ grade: "10th" });

Checking if a Document Exists

// Returns the document's _id if found, or null
const exists = await Student.exists({ name: "Ali Hassan" });

if (exists) {
  console.log("Student exists");
}

exists() is more efficient than findOne() when you only need to know if a document exists — not its full content.


Document Methods vs Model Methods

This is an important distinction in Mongoose:

Model methods — called on the model itself (Student), operate on the collection:

Student.find()
Student.findOne()
Student.create()
Student.updateOne()
Student.deleteOne()

Document methods — called on a single document instance, operate on that one document:

const student = await Student.findOne({ name: "Ali Hassan" });

student.save()    // save changes to this document
student.deleteOne() // delete this document (Mongoose 7+)
// Document method example
const student = await Student.findOne({ name: "Ali Hassan" });
student.age = 17;
await student.save(); // document method — saves this specific document

Full CRUD Example — School System

Here is a complete example using all four CRUD operations on our Student model:

const Student = require('./models/Student');

async function studentCrudDemo() {
  // CREATE
  const ali = await Student.create({
    name: "Ali Hassan",
    age: 16,
    grade: "10th",
    subjects: ["Math", "Physics", "English"],
    address: { city: "Lahore", country: "Pakistan" }
  });
  console.log('Created student:', ali._id);

  // READ — find one
  const found = await Student.findById(ali._id);
  console.log('Found:', found.name, found.grade);

  // READ — find many
  const tenthGrade = await Student.find({ grade: "10th" });
  console.log('10th grade students:', tenthGrade.length);

  // UPDATE — using findByIdAndUpdate
  const updated = await Student.findByIdAndUpdate(
    ali._id,
    { $set: { grade: "11th" }, $push: { subjects: "Computer Science" } },
    { new: true }
  );
  console.log('Updated grade:', updated.grade);
  console.log('Updated subjects:', updated.subjects);

  // DELETE
  const deleted = await Student.findByIdAndDelete(ali._id);
  console.log('Deleted:', deleted.name);
}

studentCrudDemo();

Quick Reference

OperationMethodReturns
CreateModel.create(data)The created document
Createnew Model(data) + .save()The saved document
ReadModel.find(filter)Array of documents
ReadModel.findOne(filter)One document or null
ReadModel.findById(id)One document or null
UpdateModel.updateOne(filter, update)Status object
UpdateModel.findByIdAndUpdate(id, update, { new: true })Updated document
UpdateFetch + modify + .save()The saved document
DeleteModel.deleteOne(filter)Status object
DeleteModel.findByIdAndDelete(id)Deleted document
CountModel.countDocuments(filter)Number
ExistsModel.exists(filter)_id or null

Use findByIdAndUpdate() and findOneAndUpdate() with { new: true } when you need the updated document back — for example, to send it in an API response. Use updateOne() or updateMany() when you just need to apply a change and do not need the document back — it is slightly more efficient since Mongoose does not have to return the full document.

On this page