The Mongoose queries you reach for every day, in one place. Bookmark it — find, filter, sort, paginate, update, and populate, plus the lean() tip that makes reads fast.
Finding documents
await User.find() // all documents
await User.find({ active: true }) // filter
await User.findOne({ email }) // first match
await User.findById(id) // by _id
await User.countDocuments({ active: true })
await User.exists({ email }) // truthy if any match
Query operators
// comparison
{ age: { $gt: 18, $lte: 65 } } // > and <=
{ role: { $in: ['admin', 'staff'] } } // in a set
{ score: { $ne: 0 } } // not equal
// logical
{ $or: [{ a: 1 }, { b: 2 }] }
{ $and: [{ active: true }, { age: { $gte: 18 } }] }
// element / text
{ name: { $regex: /^jo/i } } // pattern
{ tags: { $exists: true } } // field present
Projection & sorting
await User.find(filter, 'name email') // include fields
await User.find(filter).select('-password') // exclude
await User.find(filter).sort({ createdAt: -1 }) // desc
await User.find(filter).sort('name -age') // asc name, desc age
Pagination
// offset-based (simple)
const page = 2, size = 20
await User.find(filter)
.skip((page - 1) * size)
.limit(size)
// cursor-based (fast for large sets, needs an index)
await User.find({ _id: { $gt: lastId } })
.sort({ _id: 1 })
.limit(size)
Creating & updating
await User.create({ name, email }) // insert one
await User.insertMany(docs) // bulk insert
await User.updateOne({ _id: id }, { $set: { name } })
await User.updateMany({ active: false }, { $set: { active: true } })
// return the updated doc
await User.findByIdAndUpdate(id, { $inc: { visits: 1 } },
{ new: true })
// create if missing
await User.updateOne({ email }, { $set: { name } },
{ upsert: true })
// array updates
{ $push: { tags: 'new' } } // add
{ $pull: { tags: 'old' } } // remove
{ $addToSet: { tags: 'unique' } } // add if absent
Deleting
await User.deleteOne({ _id: id })
await User.deleteMany({ active: false })
await User.findByIdAndDelete(id) // returns the doc
Populate (references)
await Order.find().populate('user') // resolve ref
await Order.find().populate('user', 'name email') // pick fields
await Order.find().populate({
path: 'items',
match: { inStock: true },
options: { sort: { price: 1 } },
})
Performance
await User.find(filter).lean() // plain objects, faster reads
await User.find(filter).select('name') // fetch only what you need
await User.find(filter).sort(...).explain() // check index usage
Use lean() for read-only queries, project only the fields you need, and make sure filters and sorts are backed by an index.
FAQ
What does lean() do in Mongoose?
lean() tells Mongoose to return plain JavaScript objects instead of full Mongoose documents. It skips hydration, getters, and change tracking, so reads are noticeably faster — use it whenever you only need to read data, not modify and save it.
How do I paginate results in Mongoose?
For simple cases use .skip() and .limit(); skip((page-1)*size).limit(size). For large or frequently changing collections, prefer cursor-based pagination on an indexed field (e.g. _id greater-than the last seen value) to avoid slow deep skips.
When should I use populate()?
Use populate() to resolve referenced documents from another collection into your results. It's convenient, but each populate is an extra query — for hot paths consider the extended-reference pattern (storing the few fields you need) instead.

