MongoDB Academy · Aula

findOne versus find: cursores explicados

Recupere documentos usando findOne e percorra um cursor de find, entendendo como o MongoDB transmite grandes conjuntos de resultados.

Aula 2 de 413 etapas

findOne versus find: cursores explicados é uma aula grátis de MongoDB Academy no CoddyKit. Esta é a aula 2 de 4. Você pode ler a aula completa abaixo gratuitamente — depois pratica ao vivo no navegador com um editor de código integrado e um tutor de IA 24/7. Faz parte do caminho de aprendizado de MongoDB Academy, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de MongoDB Academy inclui 4 aulas no total.

Duas Maneiras de Ler Documentos

O MongoDB fornece dois métodos principais para ler documentos de uma collection:

  • findOne(filter, projection) — recupera o primeiro documento que corresponde ao filtro e o retorna como um objeto de documento simples (ou null se nada corresponder)
  • find(filter, projection) — recupera todos os documentos correspondentes e retorna um cursor, um iterador lento que transmite os resultados do servidor, um lote por vez

Entender quando usar cada método — e como os cursores funcionam — é fundamental para escrever consultas eficientes no MongoDB.

findOne: Simples e Direto

findOne() é a maneira mais simples de recuperar um único documento. Ele retorna o primeiro documento que corresponde ao filtro ou null se nenhum documento corresponder. Se vários documentos corresponderem, o MongoDB retornará aquele que encontrar primeiro em sua ordem interna — adicione um .sort() antes de chamar esse método se precisar de um documento específico.

Casos de uso comuns de findOne: procurar um usuário por e-mail, buscar um produto por SKU ou verificar se um registro existe. Como ele retorna um objeto simples, e não um cursor, você usa o resultado diretamente, sem iteração.

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

O Que é um Cursor?

Um cursor é um ponteiro para o conjunto de resultados de uma consulta. Quando você chama find(), o MongoDB não transfere imediatamente todos os documentos correspondentes para o cliente. Em vez disso, ele abre um cursor no servidor e envia os documentos em lotes (por padrão, 101 documentos por lote). O cliente busca o próximo lote somente quando o lote atual se esgota.

Esse design é essencial para a eficiência da memória. Se uma consulta correspondesse a 10 milhões de documentos e você os carregasse todos de uma vez, o cliente poderia travar. Com um cursor, você processa os documentos um lote por vez, mantendo constante o uso de memória, independentemente do tamanho do conjunto de resultados.

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

Iterar sobre Cursores no Node.js

Os cursores do driver do Node.js são compatíveis com vários padrões de iteração. A abordagem mais moderna é for await...of (iteração assíncrona), que lida de forma clara com o controle de fluxo e o tratamento de erros. Outra opção é cursor.toArray(), que carrega todos os resultados na memória — é conveniente, mas perigosa para conjuntos de resultados grandes.

Sempre feche os cursores quando terminar, caso interrompa a iteração antecipadamente (por exemplo, depois de encontrar o que precisa). Um cursor aberto mantém recursos no servidor do MongoDB. Use cursor.close() explicitamente ou dependa de for await...of, que fecha automaticamente o cursor quando o loop é concluído ou ocorre um erro.

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

Tamanho do Lote do Cursor e getMore

Internamente, o protocolo do cursor funciona em duas fases:

  1. O comando inicial find retorna o primeiro lote (por padrão, 101 documentos ou 16 MB, o que ocorrer primeiro)
  2. Cada lote subsequente é buscado por meio de um comando getMore usando o ID do cursor

Você pode personalizar o tamanho do lote com cursor.batchSize(n). Um tamanho de lote menor reduz o uso de memória em ambos os lados, mas exige mais idas e voltas pela rede. Um tamanho de lote maior é mais eficiente para varreduras sequenciais grandes. O padrão geralmente é ideal — ajuste-o apenas para cargas de trabalho específicas.

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

Tempo Limite do Cursor e Sessões

Por padrão, os cursores do MongoDB expiram após 10 minutos de inatividade no servidor. Se o processamento de cada lote demorar mais do que isso, o cursor será encerrado e você receberá um erro CursorNotFound ao tentar buscar o próximo lote.

Para operações de longa duração, defina noCursorTimeout: true ou use uma session para manter o cursor ativo. No entanto, esteja ciente de que noCursorTimeout mantém um cursor do servidor aberto indefinidamente — sempre feche esses cursores explicitamente quando terminar, para evitar vazamentos de recursos.

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

Encadear Modificadores em find()

O cursor retornado por find() oferece uma API fluente — você encadeia métodos para modificar a consulta antes do início da iteração. A ordem é importante para a legibilidade, mas não para a execução (o MongoDB envia todos os modificadores juntos):

  • .sort({ field: 1 }) — direção da ordenação
  • .limit(n) — quantidade máxima de documentos
  • .skip(n) — ignorar os primeiros n resultados
  • .projection({ field: 1 }) — selecionar campos
  • .hint({ index: 1 }) — forçar um índice específico
  • .maxTimeMS(ms) — interromper se a consulta demorar demais
// 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

Cursores Persistentes para Collections Limitadas

Um tipo especial de cursor chamado cursor persistente funciona apenas em collections limitadas. Diferentemente dos cursores normais, que são fechados quando todos os resultados são consumidos, um cursor persistente bloqueia e aguarda novos documentos, de forma semelhante ao comando tail -f do Unix em um arquivo de registro.

Os cursores persistentes eram o mecanismo original para transmissão de dados em tempo real no MongoDB, antes da introdução dos Change Streams. Eles ainda são úteis para acompanhar registros de forma leve em collections limitadas quando os Change Streams seriam excessivos.

// 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 vs find: Escolher Corretamente

Use esta regra prática para escolher entre findOne e find:

  • Use findOne quando: você espera exatamente um resultado (busca por chave exclusiva), precisa apenas verificar a existência ou quer o código mais simples para um endpoint de API de um único registro
  • Use find quando: a consulta pode retornar zero, um ou vários resultados; você está criando um endpoint de lista; precisa controlar o cursor (batchSize, maxTimeMS); ou está processando resultados sem carregar tudo na memória

Evite find({}).toArray() em collections grandes — isso carrega todos os resultados na memória. Em vez disso, processe-os com 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!

O Método explain() em Cursores

Adicionar .explain('executionStats') a um cursor mostra como o MongoDB executa a consulta, em vez de retornar documentos. A saída revela:

  • winningPlan.stage: IXSCAN (usa um índice) ou COLLSCAN (varredura completa — ruim para collections grandes)
  • nReturned: quantos documentos foram retornados
  • totalDocsExamined: quantos documentos o MongoDB examinou para encontrar os resultados (deve ser próximo de nReturned quando um índice é usado)
  • executionTimeMillis: tempo total de execução

Executar regularmente explain() nas suas consultas críticas é a base do ajuste de desempenho do 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)

Converter ObjectId em Respostas de API

Quando findOne ou find().toArray() retornam documentos com campos ObjectId, esses ObjectIds precisam de tratamento especial antes de serem retornados em uma resposta de API JSON. JSON.stringify serializa um ObjectId como um objeto {} (perdendo o valor) em versões antigas do driver ou como sua representação em texto nas versões mais recentes.

O padrão mais seguro é chamar explicitamente .toString() em todos os campos ObjectId dentro de uma função de mapeamento antes de enviar os dados ao cliente. Em seguida, os clientes enviam o ID de volta como texto, e você o converte com new ObjectId(idString) no servidor antes de consultar.

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

Verificação Rápida

Teste sua compreensão dos conceitos de MongoDB e bancos de dados NoSQL desta lição.

Resumo da Lição

Nesta lição, você aprendeu que findOne retorna um único documento diretamente, enquanto find retorna um cursor que transmite os resultados lentamente em lotes para evitar problemas de memória com conjuntos de resultados grandes; os cursores são compatíveis com uma API de encadeamento fluente — .sort(), .limit(), .skip(), .maxTimeMS() — que o MongoDB envia como uma única consulta otimizada; e explain('executionStats') revela se uma consulta usa um índice (IXSCAN) ou uma varredura completa da collection (COLLSCAN). A seguir, exploraremos consultas a campos aninhados e matrizes usando a notação de ponto.

Grátis para começar

Aprenda JavaScript com um tutor de IA — grátis

Escreva e execute código real no seu navegador, obtenha ajuda instantânea de um tutor de IA 24/7 e continue de onde parou na web ou no app.

Cursos
30
Aulas
120

Perguntas Frequentes

A aula “findOne versus find: cursores explicados” é grátis?

Sim — o texto completo de “findOne versus find: cursores explicados” é grátis para ler aqui na web. Para praticá-la interativamente (um editor de código integrado e um tutor de IA 24/7) e desbloquear o restante do curso de MongoDB Academy, atualize para CoddyKit PRO. O curso de MongoDB Academy inclui 4 aulas no total.

O que vou aprender em “findOne versus find: cursores explicados”?

Recupere documentos usando findOne e percorra um cursor de find, entendendo como o MongoDB transmite grandes conjuntos de resultados. Você pratica MongoDB Academy com código prático que executa diretamente no navegador, e um tutor de IA 24/7 responde suas dúvidas enquanto trabalha na aula.

Preciso ter experiência prévia para começar MongoDB Academy?

Nenhuma experiência prévia é necessária. MongoDB Academy no CoddyKit é estruturado para alunos iniciantes até avançados, então você pode começar aqui ou desde o início e aprender no seu ritmo. Esta é a aula 2 de 4.

Quanto tempo leva a aula “findOne versus find: cursores explicados”?

A maioria das aulas CoddyKit leva cerca de 5–10 minutos. Cada uma é compacta e interativa, então você faz progresso constante e retoma exatamente de onde parou entre web e app.

Posso escrever e executar código nesta aula de MongoDB Academy?

Sim. Cada aula de MongoDB Academy inclui um editor de código integrado, então você escreve e executa código real direto no navegador e recebe feedback de IA instantaneamente — nenhuma configuração local necessária.

Todas as aulas deste curso

  1. insertOne e insertMany
  2. findOne versus find: cursores explicados
  3. Consultando campos aninhados e matrizes
  4. Lendo documentos com o driver do Node.js
← Voltar para MongoDB Academy