find() and findOne()
find() and findOne() are the core read methods. find() returns a cursor over every matching document; findOne() returns a single document (or null) directly, no cursor involved. This page goes deep on the filter/projection shape and the findOneAnd* family of atomic read-and-modify operations.
The Filter Document
The first argument to both methods is the filter — a document describing which records to match. An empty filter ({}, or omitted) matches every document.
Filter shapes
db.users.find() // everything
db.users.find({ status: "active" }) // simple equality
db.users.find({ age: { $gte: 18 } }) // operator expression
db.users.find({ status: "active", age: { $gte: 18 } }) // implicit AND across fieldsProjection — Shaping the Output
The second argument is the projection — which fields to return. You can list fields to include or fields to exclude, but (with one exception) not both in the same projection.
Include vs exclude projection
// Inclusion — only name and email come back (plus _id, unless excluded)
db.users.find({}, { name: 1, email: 1 })
// Exclusion — everything EXCEPT passwordHash comes back
db.users.find({}, { passwordHash: 0 })
// Mixing inclusion and exclusion of NON-_id fields is an error:
db.users.find({}, { name: 1, email: 0 }) // ⚠ throws — can't mix, except for _idThe _id Exception
_id is the one field you can exclude alongside an otherwise-inclusion projection (or include alongside an otherwise-exclusion projection), because it's returned by default regardless of the projection's overall mode.
Excluding _id from an inclusion projection
db.users.find({}, { name: 1, email: 1, _id: 0 })
// returns ONLY { name, email } — no _id at allChaining Cursor Methods
find() chained with cursor methods
db.products
.find({ category: "widgets" }, { name: 1, price: 1 })
.sort({ price: -1 })
.limit(10)Common Query Shapes
Goal | Filter |
|---|---|
Equality |
|
Range |
|
One of several values |
|
Field exists |
|
Nested field |
|
Array contains value |
|
findOne()
findOne() is sugar for find(filter).limit(1) that also unwraps the cursor for you — it returns the matched document directly (or null if nothing matched), never a cursor.
findOne basics
const user = db.users.findOne({ email: "alice@example.com" })
if (user === null) {
// no match
}findOneAndDelete()
Atomically find and delete, returning the deleted document
const removed = db.tasks.findOneAndDelete({ status: "cancelled" })
// removed is the deleted document itself (or null) — useful for
// "claim and remove a job from a queue" style patternsfindOneAndReplace()
Atomically find and replace the whole document
const before = db.users.findOneAndReplace(
{ email: "alice@example.com" },
{ email: "alice@example.com", name: "Alice Chen", age: 30 }
)
// by default returns the document as it was BEFORE the replacefindOneAndUpdate() and returnDocument
The most commonly used of the three — atomically applies an update and gives you the document back in the same round-trip, avoiding a separate read-modify-write race condition.
returnDocument: 'before' vs 'after'
// Default — returns the document BEFORE the update was applied
db.counters.findOneAndUpdate(
{ _id: "orderNumber" },
{ $inc: { seq: 1 } }
)
// returnDocument: "after" — returns the document AFTER the update
db.counters.findOneAndUpdate(
{ _id: "orderNumber" },
{ $inc: { seq: 1 } },
{ returnDocument: "after" }
)findOneAndUpdate() with { upsert: true, returnDocument: "after" } is the idiomatic way to implement an atomic "get or create" — for example, an auto-incrementing counter document, without a race condition between separate find and update calls.findOneAnd* family only ever affects one document, and only guarantees atomicity for that single document — it is not a substitute for a multi-document transaction when you need several documents changed together.Example Session
test> db.users.findOne({ email: "alice@example.com" }, { name: 1, _id: 0 })
{ name: 'Alice Chen' }
test> db.counters.findOneAndUpdate(
... { _id: "orderNumber" },
... { $inc: { seq: 1 } },
... { upsert: true, returnDocument: "after" }
... )
{ _id: 'orderNumber', seq: 1 }find()→ cursor;findOne()→ a single document ornull.Projections are include-only or exclude-only, except
_idwhich can mix with either mode.findOneAndDelete/Replace/Update()combine a read and a write atomically, returning the document in the same round-trip.returnDocument: "after"onfindOneAndUpdate()is the standard pattern for atomic get-or-create counters and similar workflows.