MongoDB Academy · Leçon

findOne et find : comprendre les curseurs

Vous récupérerez des documents avec findOne et parcourrez un curseur find, en comprenant comment MongoDB diffuse les grands ensembles de résultats.

Leçon 2 sur 413 étapes

findOne et find : comprendre les curseurs est une leçon MongoDB Academy gratuite sur CoddyKit. Ceci est la leçon 2 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage MongoDB Academy, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours MongoDB Academy comprend 4 leçons au total.

Deux façons de lire des documents

MongoDB fournit deux méthodes principales pour lire des documents dans une collection :

  • findOne(filter, projection) — récupère le premier document correspondant au filtre et le renvoie sous forme d’objet document ordinaire (ou null si aucun document ne correspond)
  • find(filter, projection) — récupère tous les documents correspondants et renvoie un curseur, un itérateur paresseux qui transmet les résultats depuis le serveur, un lot à la fois

Comprendre quand utiliser chaque méthode — et comment fonctionnent les curseurs — est essentiel pour écrire des requêtes MongoDB efficaces.

findOne : simple et direct

findOne() est la manière la plus simple de récupérer un seul document. Il renvoie le premier document correspondant au filtre, ou null si aucun document ne correspond. Si plusieurs documents correspondent, MongoDB renvoie celui qu’il rencontre en premier selon son ordre interne ; ajoutez un .sort() avant cet appel si vous en avez besoin d’un en particulier.

Cas d’utilisation courants de findOne : rechercher un utilisateur par adresse e-mail, récupérer un produit par SKU ou vérifier qu’un enregistrement existe. Comme cette méthode renvoie un objet ordinaire plutôt qu’un curseur, vous utilisez directement le résultat sans itération.

// 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 null

Qu’est-ce qu’un curseur ?

Un curseur est un pointeur vers l’ensemble de résultats d’une requête. Lorsque vous appelez find(), MongoDB ne transfère pas immédiatement tous les documents correspondants au client. Il ouvre plutôt un curseur côté serveur et envoie les documents par lots (101 documents par lot par défaut). Le client ne récupère le lot suivant qu’une fois le lot actuel épuisé.

Cette conception est essentielle pour une utilisation efficace de la mémoire. Si une requête correspond à 10 millions de documents et que vous les chargez tous en une seule fois, le client plantera. Avec un curseur, vous traitez les documents un lot à la fois, ce qui maintient l’utilisation de la mémoire constante quelle que soit la taille de l’ensemble de résultats.

// 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 it

Itération sur les curseurs dans Node.js

Les curseurs du pilote Node.js prennent en charge plusieurs modes d’itération. L’approche la plus moderne est for await...of (itération asynchrone), qui gère proprement la régulation du flux et la gestion des erreurs. Une autre possibilité est cursor.toArray(), qui charge tous les résultats en mémoire — pratique, mais dangereuse pour les grands ensembles de résultats.

Fermez toujours les curseurs lorsque vous avez terminé si vous interrompez l’itération prématurément (par exemple, après avoir trouvé ce dont vous avez besoin). Un curseur ouvert mobilise des ressources sur le serveur MongoDB. Utilisez explicitement cursor.close(), ou utilisez for await...of, qui ferme automatiquement le curseur à la fin de la boucle ou en cas d’erreur.

// 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));

Taille des lots du curseur et getMore

En interne, le protocole des curseurs fonctionne en deux phases :

  1. La commande initiale find renvoie le premier lot (101 documents ou 16 Mo par défaut, selon la première limite atteinte)
  2. Chaque lot suivant est récupéré au moyen d’une commande getMore utilisant l’identifiant du curseur

Vous pouvez personnaliser la taille des lots avec cursor.batchSize(n). Une taille de lot plus petite réduit l’utilisation de la mémoire des deux côtés, mais nécessite davantage d’allers-retours réseau. Une taille de lot plus grande est plus efficace pour les analyses séquentielles volumineuses. La valeur par défaut est généralement optimale : ne l’ajustez que pour des charges de travail particulières.

// 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);

Expiration des curseurs et sessions

Par défaut, les curseurs MongoDB expirent après 10 minutes d’inactivité côté serveur. Si le traitement de chaque lot prend plus de temps, le curseur est supprimé et vous obtenez une erreur CursorNotFound lorsque vous essayez de récupérer le lot suivant.

Pour les opérations de longue durée, définissez noCursorTimeout: true ou utilisez une session pour maintenir le curseur actif. Sachez toutefois que noCursorTimeout laisse indéfiniment un curseur ouvert sur le serveur : fermez toujours explicitement ces curseurs lorsque vous avez terminé afin d’éviter les fuites de ressources.

// 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();
}

Chaînage des modificateurs sur find()

Le curseur renvoyé par find() prend en charge une API fluide : vous enchaînez des méthodes pour modifier la requête avant le début de l’itération. L’ordre compte pour la lisibilité, mais pas pour l’exécution (MongoDB envoie tous les modificateurs ensemble) :

  • .sort({ field: 1 }) — sens du tri
  • .limit(n) — nombre maximal de documents
  • .skip(n) — ignorer les n premiers résultats
  • .projection({ field: 1 }) — sélectionner les champs
  • .hint({ index: 1 }) — imposer un index précis
  • .maxTimeMS(ms) — interrompre la requête si elle prend trop de temps
// 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 > 5s

Curseurs persistants pour les collections plafonnées

Un type spécial de curseur appelé curseur persistant fonctionne uniquement avec les collections plafonnées. Contrairement aux curseurs normaux, qui se ferment lorsque tous les résultats ont été consommés, un curseur persistant se bloque et attend les nouveaux documents, à l’image de la commande Unix tail -f appliquée à un fichier de journal.

Les curseurs persistants constituaient le mécanisme d’origine pour la diffusion de données en temps réel dans MongoDB, avant l’introduction des flux de modifications. Ils restent utiles pour suivre légèrement les journaux de collections plafonnées, lorsque les flux de modifications seraient disproportionnés.

// 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 ou find : faire le bon choix

Utilisez cette règle générale pour choisir entre findOne et find :

  • Utilisez findOne lorsque : vous vous attendez à un seul résultat (recherche par clé unique), vous devez uniquement vérifier l’existence d’un élément ou vous souhaitez le code le plus simple pour un point d’accès d’API renvoyant un seul enregistrement
  • Utilisez find lorsque : la requête peut renvoyer zéro, un ou plusieurs résultats ; vous créez un point d’accès renvoyant une liste ; vous avez besoin de contrôler le curseur (batchSize, maxTimeMS) ; ou vous traitez les résultats sans tout charger en mémoire

Évitez find({}).toArray() sur les grandes collections : cette méthode charge tous les résultats en mémoire. Utilisez plutôt for await...of pour les traiter.

// 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!

La méthode explain() sur les curseurs

Ajouter .explain('executionStats') à un curseur montre comment MongoDB exécute la requête au lieu de renvoyer les documents. Le résultat indique :

  • winningPlan.stage : IXSCAN (utilise un index) ou COLLSCAN (analyse complète, déconseillée pour les grandes collections)
  • nReturned : le nombre de documents renvoyés
  • totalDocsExamined : le nombre de documents examinés par MongoDB pour trouver les résultats (ce nombre doit être proche de nReturned si un index est utilisé)
  • executionTimeMillis : la durée totale de l’exécution

Exécuter régulièrement explain() sur vos requêtes essentielles est la base de l’optimisation des performances de MongoDB.

// 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)

Convertir ObjectId dans les réponses d’API

Lorsque findOne ou find().toArray() renvoient des documents contenant des champs ObjectId, ces ObjectIds doivent être traités spécialement avant leur renvoi dans une réponse d’API JSON. JSON.stringify sérialise un ObjectId sous forme d’objet {} (en perdant sa valeur) dans les anciennes versions du pilote, ou sous forme de représentation textuelle dans les versions plus récentes.

La méthode la plus sûre consiste à appeler explicitement .toString() sur tous les champs ObjectId dans une fonction de mappage avant l’envoi au client. Le client renvoie ensuite l’ID sous forme de chaîne, et vous le convertissez avec new ObjectId(idString) sur le serveur avant d’effectuer la requête.

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: '...' }

Vérification rapide

Testez votre compréhension des concepts de MongoDB et des bases de données NoSQL abordés dans cette leçon.

Récapitulatif de la leçon

Dans cette leçon, vous avez appris que findOne renvoie directement un seul document, tandis que find renvoie un curseur qui transmet paresseusement les résultats par lots afin d’éviter les problèmes de mémoire avec les grands ensembles de résultats ; que les curseurs prennent en charge une API fluide par chaînage — .sort(), .limit(), .skip(), .maxTimeMS() — que MongoDB envoie sous forme d’une seule requête optimisée ; et que explain('executionStats') indique si une requête utilise un index (IXSCAN) ou effectue une analyse complète de la collection (COLLSCAN). Ensuite, nous étudierons les requêtes sur les champs imbriqués et les tableaux avec la notation par points.

Gratuit pour commencer

Apprends JavaScript avec un tuteur IA — gratuit

Écris et exécute du vrai code dans ton navigateur, obtiens de l'aide instantanée d'un tuteur IA disponible 24h/24, et reprends là où tu t'es arrêté sur le web ou dans l'app.

Cours
30
Leçons
120

Questions Fréquemment Posées

La leçon « findOne et find : comprendre les curseurs » est-elle gratuite ?

Oui — le texte complet de « findOne et find : comprendre les curseurs » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours MongoDB Academy, passe à CoddyKit PRO. Le cours MongoDB Academy comprend 4 leçons au total.

Qu'est-ce que j'apprendrai dans « findOne et find : comprendre les curseurs » ?

Vous récupérerez des documents avec findOne et parcourrez un curseur find, en comprenant comment MongoDB diffuse les grands ensembles de résultats. Tu pratiques MongoDB Academy avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.

Dois-je avoir de l'expérience pour commencer MongoDB Academy ?

Aucune expérience préalable n'est requise. MongoDB Academy sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 2 sur 4.

Combien de temps prend la leçon « findOne et find : comprendre les curseurs » ?

La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.

Peux-tu écrire et exécuter du code dans cette leçon MongoDB Academy ?

Oui. Chaque leçon MongoDB Academy inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.

Toutes les leçons de ce cours

  1. insertOne et insertMany
  2. findOne et find : comprendre les curseurs
  3. Interroger des champs imbriqués et des tableaux
  4. Lire des documents avec le pilote Node.js
← Retour à MongoDB Academy