Documentation

Introduction

find() returns a cursor: the matching documents, ready to be sorted, paged, projected and read.

const page = people.find({ city: 'Recife' })
  .sort({ age: -1, name: 1 })
  .skip(20)
  .limit(10)
  .toArray();

How a cursor works

A cursor holds references to the stored documents, not copies. Matching, sort(), skip(), limit(), length and ids() copy nothing; a document is copied only when you read it with next(), toArray(), first(), last(), forEach() or for…of. Reading one page of a million matches copies one page.

Stored documents are never changed in place (an update stores a new version), so a cursor is a snapshot: it keeps showing the documents as they were when find() ran, even if they are updated or deleted afterwards.

const cursor = people.find({ city: 'Recife' });
people.updateOne({ _id: 1 }, { $set: { age: 99 } });
cursor.toArray()[0].age;          // 31: the snapshot
people.find({ _id: 1 }).first().age;   // 99: a new find sees the change

Reading

Member Returns Description
toArray() object[] Copies of every document, as plain objects
for (const doc of cursor) object each Copies one at a time (stop early with break and the rest is never copied)
forEach(fn) void fn(copy, index) for each document
first() / last() object | null Copy of the first / last document
ids() unknown[] The _id of each document, no copies at all
length number Number of documents
hasNext() / next() / reset() Step through the documents; next() throws No more item to the cursor at the end
for (const person of people.find({ age: { $gte: 18 } })) {
  if (person.name === 'Davi') break;   // the documents after Davi are never copied
}

const ids = people.find({ tags: { $in: ['dev'] } }).ids();   // [1]

const cursor = people.find({});
while (cursor.hasNext()) {
  const doc = cursor.next();   // a copy wrapped like a document: doc.name, doc._data
}
cursor.reset();                 // start again

next() returns a document wrapper (fields read as properties, the data in doc._data); the other readers return plain objects. Both are copies.


Sorting

people.find({}).sort({ age: 1 });               // ascending
people.find({}).sort({ city: 1, age: -1 });     // by city, then age descending
people.find({}).sort({ 'address.zip': 1 });     // nested paths

1 is ascending, -1 descending; anything else throws (Sort direction of "age" must be 1 or -1). sort() sorts in place and returns the cursor. The sort is stable: documents that compare equal keep their insertion order.

Missing and null values

Missing and null values go last in both directions, like a spreadsheet:

tasks.find({}).sort({ due: 1 });                          // dated tasks first, then undated ones
tasks.find({}).sort({ due: -1 });                         // latest first, undated still last
tasks.find({}).sort({ due: 1 }, { nulls: 'first' });      // undated first

Set the default for a collection with createCollection(name, schema, { nulls: 'first' }) or collection.setOptions({ nulls }); the sort option overrides it. Before 1.6 they came first in ascending order (MongoDB's order); { nulls: 'first' } gives that order again.

Mixed types

Values of different types are ordered like MongoDB: numbers, strings, objects, arrays, booleans, dates (then missing / null, or first with nulls: 'first').

Language aware text

Strings compare by code point by default ('B' < 'a', 'é' > 'z'). Pass a collation, with the options of Intl.Collator, to sort by language rules:

people.find({}).sort({ name: 1 }, { collation: { locale: 'pt-BR', sensitivity: 'base' } });
files.find({}).sort({ name: 1 }, { collation: { locale: 'en', numeric: true } });   // 'item 2' before 'item 10'

Paging

const pageSize = 20;
const page = n => people.find({}).sort({ name: 1 }).skip(n * pageSize).limit(pageSize).toArray();
Member Description
skip(count) Drops the first count documents (apply after sort, like MongoDB)
limit(count) Keeps at most count; 0 means no limit

Both need a non-negative integer. The total for a pager is people.count(query) or the cursor's length before skip.


Projection

Choose the fields of the copies you get back:

people.find({}, { name: 1, age: 1 });   // only name, age and _id
people.find({}, { name: 1, _id: 0 });   // only name
people.find({}, { tags: 0 });           // everything except tags
people.find({}).projection({ name: 1 }); // same as the second argument

Inclusion (1) and exclusion (0) cannot be mixed, except for _id; projection works on top level fields (Projection supports top level fields only). It applies to the copies: stored documents keep every field, and sorting can use fields the projection hides.


Cheat sheet

const c = people.find(query, projection);
c.sort({ field: 1 }, { nulls: 'last', collation: { locale: 'pt-BR' } });
c.skip(40).limit(20);
c.length;  c.ids();                        // no copies
c.toArray();  c.first();  c.last();        // copies
for (const doc of c) {}  c.forEach(fn);     // copies, one at a time
c.hasNext();  c.next();  c.reset();