findOne versus find: cursors uitgelegd
U haalt documenten op met findOne en doorloopt een find-cursor, waarbij u begrijpt hoe MongoDB grote resultatensets streamt.
findOne versus find: cursors uitgelegd is een gratis MongoDB Academy-les op CoddyKit. Dit is les 2 van 4. Je kunt de volledige les hieronder gratis lezen en daarna in de browser praktisch oefenen met een ingebouwde code-editor en een AI-begeleider die 24/7 beschikbaar is. Deze les maakt deel uit van het leertraject MongoDB Academy. Je voortgang wordt gesynchroniseerd op het web en in de CoddyKit-app. De cursus MongoDB Academy bevat in totaal 4 lessen.
Twee manieren om documenten te lezen
MongoDB biedt twee primaire methoden om documenten uit een verzameling te lezen:
- findOne(filter, projection) — haalt het eerste document op dat overeenkomt met het filter en retourneert het als een gewoon documentobject (of
nullals niets overeenkomt) - find(filter, projection) — haalt alle overeenkomende documenten op en retourneert een cursor, een luie iterator die de resultaten batch voor batch van de server streamt
Begrijpen wanneer je elke methode gebruikt en hoe cursors werken, is essentieel voor het schrijven van efficiënte MongoDB-query's.
findOne: eenvoudig en direct
findOne() is de eenvoudigste manier om één document op te halen. Deze methode retourneert het eerste document dat overeenkomt met het filter, of null als geen enkel document overeenkomt. Als meerdere documenten overeenkomen, retourneert MongoDB het document dat het volgens de interne volgorde als eerste tegenkomt—voeg vóór deze aanroep een .sort() toe als je een specifiek document nodig hebt.
Veelvoorkomende toepassingen van findOne zijn: een gebruiker op e-mailadres opzoeken, een product op SKU ophalen of controleren of een record bestaat. Omdat de methode een gewoon object en geen cursor retourneert, gebruik je het resultaat rechtstreeks zonder iteratie.
// findOne by _id (most common lookup)
const user = await db.collection('users').findOne(
{ _id: ObjectId('64a2f3b1...') }
);
if (!user) {
throw new Error('User not found');
}
console.log(user.name); // 'Alice'
// findOne with a filter
const admin = await db.collection('users').findOne({ role: 'admin' });
// Returns ONE admin doc (undefined order), or nullWat is een cursor?
Een cursor is een verwijzing naar de resultatenset van een query. Wanneer je find() aanroept, draagt MongoDB niet onmiddellijk alle overeenkomende documenten over aan de client. In plaats daarvan opent de server een cursor en verzendt deze documenten in batches (standaard 101 documenten per batch). De client haalt de volgende batch pas op wanneer de huidige batch is verbruikt.
Dit ontwerp is essentieel voor efficiënt geheugengebruik. Als een query 10 miljoen documenten vindt en je die allemaal tegelijk laadt, zou de client crashen. Met een cursor verwerk je documenten batch voor batch, waardoor het geheugengebruik constant blijft, ongeacht de omvang van de resultatenset.
// find() returns a cursor, not documents
const cursor = db.collection('orders').find({ status: 'pending' });
// No data fetched yet!
// Data flows as you iterate:
for await (const order of cursor) {
// Each iteration fetches from server in batches
console.log(order._id);
}
// Cursor is exhausted — server releases itCursors doorlopen in Node.js
Cursors van het Node.js-stuurprogramma ondersteunen meerdere iteratiepatronen. De modernste aanpak is for await...of (asynchrone iteratie), die tegendruk en foutafhandeling netjes afhandelt. Een alternatief is cursor.toArray(), dat alle resultaten in het geheugen laadt—handig, maar gevaarlijk voor grote resultatensets.
Sluit cursors altijd wanneer je klaar bent als je vroegtijdig uit de iteratie stapt (bijvoorbeeld nadat je hebt gevonden wat je nodig hebt). Een geopende cursor houdt bronnen op de MongoDB-server bezet. Gebruik expliciet cursor.close(), of vertrouw op for await...of, dat de cursor automatisch sluit wanneer de lus is voltooid of er een fout optreedt.
// Pattern 1: async for...of (recommended)
const cursor = db.collection('products').find({ inStock: true });
for await (const product of cursor) {
await processProduct(product);
}
// Pattern 2: toArray() - loads all into memory
const products = await db.collection('products')
.find({ inStock: true }).toArray();
// Pattern 3: forEach
await cursor.forEach(product => console.log(product.name));Batchgrootte van cursors en getMore
Intern werkt het cursorprotocol in twee fasen:
- De eerste opdracht
findretourneert de eerste batch (standaard 101 documenten of 16 MB, afhankelijk van wat het eerst wordt bereikt) - Elke volgende batch wordt opgehaald via een opdracht
getMoremet de cursor-ID
Je kunt de batchgrootte aanpassen met cursor.batchSize(n). Een kleinere batchgrootte vermindert het geheugengebruik aan beide kanten, maar vereist meer netwerkretouren. Een grotere batchgrootte is efficiënter voor grote opeenvolgende scans. De standaardwaarde is meestal optimaal—pas deze alleen aan voor specifieke werklasten.
// Set a custom batch size (rarely needed)
const cursor = db.collection('logs')
.find({})
.batchSize(500);
// Count documents in a cursor without loading them
// (MongoDB 4.4+ supports .count() on cursor for backwards compat)
// Prefer countDocuments() for accurate counts:
const count = await db.collection('logs').countDocuments({});
console.log('Total logs:', count);Time-out en sessies van cursors
Standaard verlopen MongoDB-cursors na 10 minuten inactiviteit aan de serverzijde. Als het verwerken van elke batch langer duurt, wordt de cursor beëindigd en krijg je een CursorNotFound-fout wanneer je de volgende batch probeert op te halen.
Stel voor langlopende bewerkingen noCursorTimeout: true in of gebruik een sessie om de cursor actief te houden. Houd er wel rekening mee dat noCursorTimeout een servercursor onbeperkt geopend houdt—sluit deze cursors altijd expliciet wanneer je klaar bent om lekken van bronnen te voorkomen.
// Long-running cursor that won't time out
const cursor = db.collection('bigCollection').find(
{},
{ noCursorTimeout: true }
);
try {
for await (const doc of cursor) {
await slowProcessing(doc); // Takes > 10 minutes total
}
} finally {
// Always close explicitly when using noCursorTimeout
await cursor.close();
}Modifiers aan find() koppelen
De cursor die door find() wordt geretourneerd, ondersteunt een vloeiende API—je koppelt methoden aan elkaar om de query aan te passen voordat de iteratie begint. De volgorde is belangrijk voor de leesbaarheid, maar niet voor de uitvoering (MongoDB verzendt alle modifiers samen):
.sort({ field: 1 })— sorteerrichting.limit(n)— maximumaantal documenten.skip(n)— eerste n resultaten overslaan.projection({ field: 1 })— velden selecteren.hint({ index: 1 })— een specifieke index afdwingen.maxTimeMS(ms)— afbreken als de query te lang duurt
// Full chained query: filter → sort → skip → limit → projection
const page2Products = db.collection('products').find(
{ category: 'Electronics', inStock: true },
{ name: 1, price: 1, _id: 0 } // projection as 2nd arg
)
.sort({ price: -1 }) // Descending price
.skip(20) // Skip page 1 (20 items)
.limit(20) // Page size 20
.maxTimeMS(5000); // Abort if > 5sDoorlopende cursors voor begrensde verzamelingen
Een speciaal cursortype, een doorlopende cursor genoemd, werkt alleen voor begrensde verzamelingen. In tegenstelling tot normale cursors, die sluiten wanneer alle resultaten zijn verbruikt, blokkeert een doorlopende cursor en wacht deze op nieuwe documenten, vergelijkbaar met de opdracht tail -f van Unix voor een logbestand.
Doorlopende cursors waren het oorspronkelijke mechanisme voor realtimegegevensstreaming in MongoDB, voordat Change Streams werden geïntroduceerd. Ze zijn nog steeds nuttig voor eenvoudig volgen van logbestanden in begrensde verzamelingen, wanneer Change Streams te zwaar zijn.
// Tailable cursor on a capped collection
const tailCursor = db.collection('appLogs').find(
{},
{ tailable: true, awaitData: true }
);
// Blocks and awaits new log entries indefinitely
for await (const log of tailCursor) {
console.log('[' + log.level + '] ' + log.message);
// Prints each new log as it is inserted
}findOne versus find: de juiste keuze maken
Gebruik deze vuistregel om te kiezen tussen findOne en find:
- Gebruik findOne wanneer: je precies één resultaat verwacht (opzoeken via een unieke sleutel), alleen wilt controleren of iets bestaat, of de eenvoudigste code wilt voor een API-eindpunt dat één record retourneert
- Gebruik find wanneer: de query nul, één of meerdere resultaten kan retourneren; je een lijst-eindpunt bouwt; je controle over de cursor nodig hebt (batchSize, maxTimeMS); of je resultaten verwerkt zonder alles in het geheugen te laden
Vermijd find({}).toArray() voor grote verzamelingen—deze methode laadt alle resultaten in het geheugen. Verwerk ze in plaats daarvan met for await...of.
// GOOD: findOne for unique key lookup
const user = await db.collection('users').findOne({ email: 'alice@test.com' });
// GOOD: find with streaming for large sets
for await (const doc of db.collection('users').find({ active: true })) {
await sendNewsletter(doc);
}
// BAD: loading millions of docs into memory
const allUsers = await db.collection('users').find({}).toArray();
// Could OOM crash your server!De methode explain() op cursors
Als je .explain('executionStats') aan een cursor toevoegt, zie je hoe MongoDB de query uitvoert in plaats van documenten te retourneren. De uitvoer toont:
winningPlan.stage:IXSCAN(gebruikt een index) ofCOLLSCAN(volledige scan—slecht voor grote verzamelingen)nReturned: hoeveel documenten zijn geretourneerdtotalDocsExamined: hoeveel documenten MongoDB heeft bekeken om resultaten te vinden (dit hoort dicht bij nReturned te liggen als een index wordt gebruikt)executionTimeMillis: totale uitvoeringstijd
Het regelmatig uitvoeren van explain() op je kritieke query's vormt de basis voor het afstellen van de MongoDB-prestaties.
// Check query execution plan
const plan = await db.collection('users')
.find({ email: 'alice@test.com' })
.explain('executionStats');
console.log(plan.queryPlanner.winningPlan.stage);
// 'IXSCAN' if email is indexed, 'COLLSCAN' if not
console.log(plan.executionStats.nReturned); // 1
console.log(plan.executionStats.totalDocsExamined); // 1 (indexed) or 50000 (COLLSCAN)ObjectId converteren in API-antwoorden
Wanneer findOne of find().toArray() documenten met ObjectId-velden retourneert, moeten deze ObjectIds speciaal worden verwerkt voordat je ze in een JSON API-antwoord retourneert. JSON.stringify serialiseert een ObjectId in oudere versies van het stuurprogramma als een object {} (waardoor de waarde verloren gaat), of in nieuwere versies als de tekenreeksrepresentatie ervan.
De veiligste aanpak is om in een toewijzingsfunctie expliciet .toString() aan te roepen op alle ObjectId-velden voordat je ze naar de client verzendt. Clients sturen de ID vervolgens als tekenreeks terug en op de server converteer je deze met new ObjectId(idString) voordat je de query uitvoert.
function toPublicDoc(doc) {
if (!doc) return null;
return {
...doc,
_id: doc._id.toString(), // ObjectId -> string for JSON
authorId: doc.authorId ? doc.authorId.toString() : null
};
}
// Usage:
const post = await db.collection('posts').findOne({ slug: 'intro' });
res.json(toPublicDoc(post));
// Client receives: { _id: '64a2f3b1c9e7...', title: '...' }Korte controle
Test je begrip van de concepten uit deze les over MongoDB en NoSQL-databases.
Samenvatting van de les
In deze les heb je geleerd: findOne retourneert rechtstreeks één document, terwijl find een cursor retourneert die resultaten lui in batches streamt om geheugenproblemen met grote resultaatsets te voorkomen, cursors ondersteunen een vloeiende keten-API—.sort(), .limit(), .skip(), .maxTimeMS()—die MongoDB als één geoptimaliseerde query verstuurt, en explain('executionStats') laat zien of een query een index gebruikt (IXSCAN) of een volledige verzameling doorloopt (COLLSCAN). Hierna gaan we dieper in op het opvragen van geneste velden en arrays met puntnotatie.
Leer JavaScript met een AI-tutor — gratis
Schrijf echte code en voer die uit in je browser, krijg direct hulp van een AI-tutor die 24/7 beschikbaar is en ga verder waar je gebleven bent op het web of in de app.
- Cursussen
- 30
- Lessen
- 120
Veelgestelde vragen
Is de les “findOne versus find: cursors uitgelegd” gratis?
Ja — de volledige tekst van “findOne versus find: cursors uitgelegd” kun je hier gratis op het web lezen. Als je interactief wilt oefenen met een ingebouwde code-editor en een AI-begeleider die 24/7 beschikbaar is, en de rest van de cursus MongoDB Academy wilt ontgrendelen, kun je upgraden naar CoddyKit PRO. De cursus MongoDB Academy bevat in totaal 4 lessen.
Wat leer ik in “findOne versus find: cursors uitgelegd”?
U haalt documenten op met findOne en doorloopt een find-cursor, waarbij u begrijpt hoe MongoDB grote resultatensets streamt. Je oefent met MongoDB Academy door code rechtstreeks in de browser uit te voeren. Een AI-begeleider die 24/7 beschikbaar is beantwoordt je vragen terwijl je de les doorwerkt.
Heb ik ervaring nodig om met MongoDB Academy te beginnen?
Ervaring vooraf is niet nodig. MongoDB Academy op CoddyKit is opgebouwd voor beginners tot gevorderden, zodat je hier of bij het begin kunt starten en in je eigen tempo kunt leren. Dit is les 2 van 4.
Hoe lang duurt de les “findOne versus find: cursors uitgelegd”?
De meeste lessen van CoddyKit duren ongeveer 5–10 minuten. Elke les is kort en interactief, zodat je gestaag vooruitgaat en op het web en in de app precies verdergaat waar je was gebleven.
Kan ik code schrijven en uitvoeren in deze les over MongoDB Academy?
Ja. Elke les over MongoDB Academy bevat een ingebouwde code-editor, zodat je rechtstreeks in je browser echte code kunt schrijven en uitvoeren en direct feedback van AI krijgt — lokale installatie is niet nodig.
Alle lessen in deze cursus
- insertOne en insertMany
- findOne versus find: cursors uitgelegd
- Geneste velden en arrays queryen
- Documenten lezen met de Node.js-driver