Skip to content

Finding documents

Reading is where a database earns its keep, and MongoDB gives you two doors into the same room. findOne is for when you expect a single document — “who is the member named Grace?” — and hands you that one document or nothing at all. find is for when you might get several — “which members joined in 2023?” — and hands you a cursor you can iterate over. Both take the same kind of filter, a document that describes which records you want.

Throughout this lesson assume the members collection already holds the people we inserted earlier. We will ask it questions of increasing precision.

The difference is shape, not syntax. findOne returns at most one document directly. find returns a cursor — a lazy handle the driver iterates to pull documents in batches — so you usually turn it into an array (or loop over it) to see the results.

db.members.findOne({ name: "Grace" })

The single matching document comes back in full, including the _id:

{
"_id": "65f0b3d4e4b0a1c2e4b0a1d0",
"name": "Grace",
"joined": 2024,
"fines": 0
}

An empty filter matches everything, which is how you read a whole collection. With find you get a cursor over all of it:

db.members.find({})

A filter that names a field and a value means “this field equals this value.” Stack several fields in one filter and MongoDB treats them as an implicit AND — a document must match every condition to be returned. Here we ask for members who joined in 2023 and have no outstanding fines:

db.members.find({ joined: 2023, fines: 0 })

The result is a list of the documents that satisfied both conditions:

[
{
"_id": "65f0b3d4e4b0a1c2e4b0a1d2",
"name": "Margaret",
"joined": 2023,
"fines": 0
}
]

By default a read returns every field of every matching document. A projection lets you ask for just the fields you care about, which trims the data sent over the wire. You pass a second document where 1 means “include this field.” Here we want only names, and we explicitly turn the _id off by setting it to 0:

db.members.find({ joined: 2023 }, { name: 1, _id: 0 })

Now each result carries only the requested field:

[{ "name": "Margaret" }]

In Compass: type a filter into the Filter bar at the top of the Documents tab and press Find; open the Options panel to fill in a projection without writing the call by hand.

  • find returns a cursor, not a list. Nothing is fetched until you iterate it (or call a helper like toArray). A findOne is effectively a cursor that stops after the first document.
  • The _id field is included by default in every projection. If you do not want it, you have to switch it off explicitly with _id: 0.
  • Don’t mix 1 and 0 in the same projection except for _id. Either list the fields you want, or list the fields you want to drop — not both.
  • A filter that matches nothing returns an empty cursor (or, for findOne, a null result). That is a normal answer, not an error.
What does find return, compared with findOne?
In a filter with two fields, how are the conditions combined?
How do you keep the _id out of a projection?