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:
- Registers the schema under the name
"Student" - 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 name | Collection name |
|---|---|
Student | students |
Teacher | teachers |
Course | courses |
Enrollment | enrollments |
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); // 3new 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 databaseThis 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); // 1updateMany()
Updates all matching documents:
const result = await Student.updateMany(
{ grade: "10th" },
{ $set: { enrolled: true } }
);
console.log(result.modifiedCount); // number of documents updatedfindByIdAndUpdate()
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); // 1deleteMany()
const result = await Student.deleteMany({ enrolled: false });
console.log(result.deletedCount); // number deletedfindByIdAndDelete()
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 documentFull 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
| Operation | Method | Returns |
|---|---|---|
| Create | Model.create(data) | The created document |
| Create | new Model(data) + .save() | The saved document |
| Read | Model.find(filter) | Array of documents |
| Read | Model.findOne(filter) | One document or null |
| Read | Model.findById(id) | One document or null |
| Update | Model.updateOne(filter, update) | Status object |
| Update | Model.findByIdAndUpdate(id, update, { new: true }) | Updated document |
| Update | Fetch + modify + .save() | The saved document |
| Delete | Model.deleteOne(filter) | Status object |
| Delete | Model.findByIdAndDelete(id) | Deleted document |
| Count | Model.countDocuments(filter) | Number |
| Exists | Model.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.