Chapter 02
Reading and writing documents
Inserting, finding, updating and deleting documents: filters and query operators, projections, sorting and paging, update operators, upserts and cursors.
insertOne and insertMany
Also called insert, create, insertedId
- What
- insertOne adds one document and returns its _id; insertMany adds an array of documents in one round trip. Missing _id values are generated for you.
- Why
- Bulk inserts with insertMany are much faster than a loop of insertOne calls, which pays a network round trip each time.
db.products.insertOne({
name: "Desk lamp", price: 25, tags: ["home"]
})
db.products.insertMany(
[{ name: "Mug", price: 8 }, { name: "Pen", price: 2 }],
{ ordered: false } // keep going after an error
)Watch out
Assuming a failed insertMany inserted nothing: with ordered inserts, documents before the failing one are already saved.
find and query filters
Also called find, findOne, query, filter document
- What
- find takes a filter document and returns every matching document; findOne returns the first match. Fields listed in one filter are combined with AND.
- Why
- Every read starts here, and filtering in the database sends back only what you need and lets it use indexes.
db.products.find({ category: "books", inStock: true })
db.users.findOne({ email: "sara@example.com" })
db.products.find({}) // every documentWatch out
Repeating a key in one filter object, such as two price conditions: the second silently replaces the first.
Query operators
Also called $gt, $lt, $in, $ne, $or, $exists, $regex, comparison operators
- What
- Operators starting with $ express conditions: $eq, $ne, $gt, $gte, $lt, $lte, $in and $nin compare values, $and, $or and $not combine conditions, $exists and $type check fields.
- Why
- They turn a filter from exact matches into ranges, lists and alternatives, the MongoDB version of a SQL WHERE clause.
db.products.find({
price: { $gte: 10, $lt: 50 },
category: { $in: ["books", "games"] },
$or: [{ onSale: true }, { stock: { $gt: 100 } }],
discontinuedAt: { $exists: false }
})Watch out
Expecting { field: null } to match only nulls: it also matches documents where the field is missing.
Projection
Also called fields, select fields, exclude fields
- What
- The second argument to find, listing the fields to return (1) or leave out (0). _id is returned unless you exclude it.
- Why
- Returning only the fields you need saves bandwidth and memory, and it is what makes covered queries possible.
db.users.find(
{ country: "PK" },
{ name: 1, email: 1, _id: 0 }
)Watch out
Mixing 1 and 0 in one projection: it is an error, apart from excluding _id.
sort, skip and limit
Also called sort, limit, skip, pagination, offset
- What
- Cursor methods that order results by one or more fields (1 ascending, -1 descending), skip a number of documents and cap how many come back.
- Why
- Lists, feeds and "top N" queries all depend on a defined order. Without sort, the order of results is not defined.
db.products.find({ category: "books" })
.sort({ price: -1, _id: 1 })
.skip(20)
.limit(10)Watch out
Paging deep with skip: skip(100000) still walks past 100,000 documents on every request.
Update operators
Also called $set, $inc, $unset, $rename, $currentDate, updateOne, updateMany
- What
- updateOne and updateMany take a filter and an update document of operators: $set sets fields, $unset removes them, $inc adds to a number, $currentDate stamps the time.
- Why
- Operators change only the named fields atomically on the server, so two users incrementing a counter at once both count.
db.products.updateOne(
{ _id: 42 },
{
$inc: { stock: -1 },
$set: { lastSoldAt: new Date() }
}
)Watch out
Reading a document, changing it in code and saving it back, which loses concurrent changes that $inc would have kept.
Upsert
Also called upsert: true, $setOnInsert, insert or update
- What
- An update with upsert: true inserts a new document when nothing matches the filter, built from the filter and the update. $setOnInsert sets fields only on that insert.
- Why
- Counters, "save settings" actions and syncing data need insert-or-update in one call, without a race between checking and writing.
db.pageViews.updateOne(
{ page: "/pricing", day: "2026-09-28" },
{
$inc: { views: 1 },
$setOnInsert: { firstSeenAt: new Date() }
},
{ upsert: true }
)Watch out
Upserting without a unique index on the filter fields, so two concurrent requests can both insert a document.
replaceOne versus updateOne
Also called replaceOne, replace document, full update
- What
- replaceOne swaps the whole document, apart from _id, for the one you pass. updateOne changes only the fields named by its operators and refuses a plain document.
- Why
- Choosing the wrong one either deletes fields you meant to keep or makes you rebuild whole documents for a one-field change.
// keeps every other field
db.users.updateOne({ _id: 7 }, { $set: { name: "Ali" } })
// the document becomes exactly this
db.users.replaceOne({ _id: 7 }, { name: "Ali" })Watch out
Using replaceOne to change one field and wiping the email, roles and everything else in the document.
deleteOne and deleteMany
Also called delete, remove, drop, deleteMany
- What
- deleteOne removes the first matching document and deleteMany removes every match. drop removes the whole collection with its indexes.
- Why
- They are the commands most likely to cause real damage, so run the same filter through find or countDocuments first.
const filter = { status: "draft",
updatedAt: { $lt: ISODate("2026-01-01") } }
db.posts.countDocuments(filter) // check first
db.posts.deleteMany(filter)Watch out
Calling deleteMany({}) with an empty filter, which deletes every document in the collection.
Cursor
Also called batch, it, toArray, getMore
- What
- find returns a cursor, a pointer to the result that fetches documents from the server in batches as you iterate. mongosh prints the first 20; type it for more.
- Why
- Cursors let you stream results far larger than memory, one batch at a time, instead of loading everything at once.
const cur = db.orders.find({ status: "paid" })
while (cur.hasNext()) {
const o = cur.next()
print(o._id, o.total)
}Watch out
Calling toArray on a huge result, which loads every document into memory, or leaving a cursor idle until the server times it out.