MongoDB with Mongoose
Mongoose is an Object Data Modeling library that gives MongoDB structure: you define a Schema for your documents, compile it into a Model, and use it to create, find, update, and delete data with built-in validation.
Learn MongoDB with Mongoose in our free Node.js course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick reference.
Part of the free Node.js course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
In this lesson you'll connect to MongoDB, design a schema with types and validators, run the full set of CRUD operations and queries, validate input, and link documents together with refs and populate — the data layer behind most Node.js APIs.
What You'll Learn in This Lesson
1️⃣ Connect, Schema, Model
Three steps get you started. mongoose.connect(uri) opens a (cached) connection to your database. A Schema describes each field's type and rules. And mongoose.model('User', schema) compiles that schema into a Model — the object you actually use to read and write the users collection.
2️⃣ CRUD: Create, Read, Update, Delete
Every Model gives you the same core methods: create to insert, find / findById to read, updateOne to change, and deleteOne to remove. The runnable block below uses a stand-in with the identical API , so you can see real output for the full create → read → update → delete cycle:
Here are the real Mongoose calls — note the method names are exactly the same as above, plus query helpers like $gte , .sort() , .limit() , and .select() :
3️⃣ Validation, Refs & Populate
A big reason to use Mongoose is schema validation . Rules like required , min , and enum are checked before a document is saved, and a failure throws a ValidationError listing what's wrong. The stand-in below produces the same kind of messages so you can see validation in action:
Finally, documents relate to each other through refs . A field typed as an ObjectId with a ref stores another document's id; calling .populate('field') swaps that id for the full document — Mongoose's answer to a join:
Your turn. Fill in the two ___ blanks so the program creates and finds a book, then run it.
Mini-Challenge: A Tiny Task List
No blanks this time — just a brief and an outline. Write it yourself, run it, and check your output. This ties together create, filtered find, and delete — the same methods real Mongoose models expose.
Common Errors (and the fix)
- "MongooseServerSelectionError" — Mongoose can't reach the database. Check the connection URI, that MongoDB is actually running, and any IP allow-list (Atlas) or firewall.
- "ValidationError: Path is required" — You tried to save a document missing a required field. Validate input first and return a 400 with the message.
- "E11000 duplicate key error" — A unique field (like email) already exists. This is a database index error, not a schema validation error — catch it separately.
- Update ignored my validators — updateOne / findByIdAndUpdate skip validators by default. Pass { runValidators: true } .
- find() result has no .name — find() returns an array. Use findOne / findById for a single doc, or index into the array.
Pro Tips
- 💡 Connect once at startup: Mongoose caches the connection, so call connect a single time, not per request.
- 💡 Let the schema guard your data: mark fields required and add enum / min so bad data is rejected before it lands in the DB.
- 💡 Use lean() for read-heavy queries: find().lean() returns plain objects (no Mongoose overhead) when you only need to read.
📋 Quick Reference — Mongoose
Task
Syntax
Connect
await mongoose.connect(uri)
Model
mongoose.model('User', schema)
Create
await User.create({ ... })
Find many
await User.find({ role: 'admin' })
Find one by id
await User.findById(id)
Update
await User.updateOne(filter, { $set })
Delete
await User.deleteOne(filter)
Populate ref
.populate('author')
Frequently Asked Questions
🎉 Lesson Complete!
- ✅ mongoose.connect opens one cached connection at startup
- ✅ A Schema defines field types and rules; a Model reads and writes a collection
- ✅ create , find , findById , updateOne , deleteOne cover CRUD
- ✅ Queries take filters and chain .sort() , .limit() , .select()
- ✅ Schema validators reject bad data before it's saved
- ✅ ref + populate link documents together like a join
- ✅ Next lesson: Deploying Node — ship your app with PM2 and Docker
Practice quiz
What is the purpose of a Mongoose Schema?
- To connect to the database
- To describe the shape of your documents
- To start a server
- To hash passwords
Answer: To describe the shape of your documents. A Schema defines field types, required rules, defaults, and validators.
Which Mongoose call connects to a MongoDB database?
- mongoose.open()
- mongoose.start()
- mongoose.connect()
- mongoose.link()
Answer: mongoose.connect(). mongoose.connect('mongodb://...') opens the connection.
How do you turn a schema into a Model named User?
- mongoose.model('User', userSchema)
- new User(schema)
- schema.toModel('User')
- mongoose.schema('User')
Answer: mongoose.model('User', userSchema). mongoose.model('User', userSchema) compiles the schema into a Model.
Mongoose maps the model 'User' to which collection name?
- User
- USERS
- users
- User_collection
Answer: users. Mongoose lowercases and pluralizes, so 'User' maps to the 'users' collection.
Which method inserts a new document and runs validation first?
- User.insert()
- User.create()
- User.add()
- User.new()
Answer: User.create(). User.create(data) inserts a document after running schema validation.
Which method finds a single document by its _id?
- User.find()
- User.getOne()
- User.byId()
- User.findById()
Answer: User.findById(). User.findById(id) returns one document by its _id (or null).
What does User.find() with NO filter return?
- All documents in the collection
- Just the first document
- An error
- Only documents created today
Answer: All documents in the collection. find() with no filter returns every document in the collection.
Which schema option auto-adds createdAt and updatedAt fields?
- { versionKey: true }
- { timestamps: true }
- { dates: true }
- { auto: true }
Answer: { timestamps: true }. Passing { timestamps: true } adds createdAt and updatedAt automatically.
Which query operator selects ages greater than or equal to 18?
- { age: { $eq: 18 } }
- { age: { $lt: 18 } }
- { age: { $gte: 18 } }
- { age: '>=18' }
Answer: { age: { $gte: 18 } }. $gte means greater-than-or-equal, e.g. { age: { $gte: 18 } }.
What error does Mongoose throw when a required field is missing or a value breaks a rule?
- TypeError
- SyntaxError
- RangeError
- ValidationError
Answer: ValidationError. Schema rules (required, min, enum) cause a ValidationError.
Continue this course
- Previous: Logging with morgan & pino
- Next: Deploying Node