Mongoose: Como find() funciona

alguma confusão na internet sobre o que acontece quando você chama Model.find() no Mongoose. Não se engane, Model.find() faz o que você espera: encontra todos os documentos que correspondem a uma consulta. Mas há alguma confusão entre Model.find() e Query#find(), opções de configuração, suporte a Promises. Neste artigo, fornecerei uma visão geral conceitual do que acontece quando você chama Model.find() para poder responder a perguntas semelhantes.

Configuração

const mongoose = require('mongoose');run().catch(error => console.log(error.stack));async function run() {
await mongoose.connect('mongodb://localhost:27017/test', { useNewUrlParser: true });
// Limpa o banco de dados todas as vezes.
// Apenas para nosso exemplo.
// Não faça isso em prod! :)
await mongoose.connection.dropDatabase();
const customerSchema = new mongoose.Schema({ name: String, age: Number, email: String });
const Customer = mongoose.model('Customer', customerSchema);
await Customer.create({ name: 'A', age: 30, email: 'a@foo.bar' });
await Customer.create({ name: 'B', age: 28, email: 'b@foo.bar' });
// Acha todos os Customers
const docs = await Customer.find();
console.log(docs);
}

Modelos e Consultas

Já o Model.find() retorna uma instância da classe Query do Mongoose. A classe Query representa uma operação CRUD crua (raw) que você pode enviar para o MongoDB. Ele fornece uma interface encadeada para criar consultas mais sofisticadas. Você não instancia uma Query diretamente, o Customer.find() instancia um para você.

const query = Customer.find();
query instanceof mongoose.Query; // true
const docs = await query; // Pega os documentos

Então, Model.find() retorna uma instância de Query. Você pode encadear chamadas .find() para adicionar operadores de consulta adicionais, também conhecidos como filtros. Por exemplo, as duas consultas a seguir encontrarão todos os clientes que o email contenha 'foo.bar' e cujo age mínimo seja 30.

// Primeiro parâmetro do "find()" é um objeto que contem os operadores da consulta, veja:
// https://docs.mongodb.com/manual/reference/operator/query/
Customer.find({ email: /foo\.bar/, age: { $gte: 30 } });
// Equivalente:
Customer.find({ email: /foo\.bar/ }).find({ age: { $gte: 30 } });

Os objetos de consulta têm vários auxiliares para criar operações sofisticadas de CRUD. Os mais utilizados são Query#sort(), Query#limit() e Query#skip().

// Encontre o primeiro customer em ordem alfabética, nesse caso "A".
// Você pode usar `{ name: -1 }` para sortear em ordem reversa.
const res = await Customer.find({}).sort({ name: 1 }).limit(1);
// Encontre o segundo customer em ordem alfabética, nesse caso "B"
const res2 = await Customer.find({}).sort({ name: 1 }).skip(1).limit(1);

Uma grande vantagem do Mongoose é que o Mongoose lança consultas para corresponder ao esquema do modelo. Isso significa que você não precisa converter explicitamente cadeias de caracteres para ObjectIds ou se preocupar com as nuances de converter cadeias de caracteres em números.

// Mongoose irá converter `_id` de string para ObjectID e `age.$gte` para número, ou irá jogar um error se ele falhar em qualquer um desses valores
Customer.find({ _id: res[0]._id.toHexString(), age: { $gte: '25' } });

Opções de configuração

const query = Customer.find().sort({ name: 1 }).limit(1);
query.getOptions(); // { sort: { name: 1 }, limit: 1 }

A função Model.find() usa três argumentos que ajudam a inicializar uma consulta sem encadeamento. O primeiro argumento é o filtro de consulta (também conhecido como conditions). O segundo argumento é a projeção da consulta, que define quais campos incluir ou excluir da consulta. Por exemplo, se você deseja excluir o campo email por questões de privacidade, pode usar uma das sintaxes abaixo.

// Explicitamente remove o `email` usando o 2º argumento. Use `email: 1` para incluir _apenas_ a propriedade `email`
Customer.find({}, { email: 0 });
// Abordagem equivalente usando encadeamento
Customer.find().select({ email: 0 });

O terceiro argumento de Model.find() é as opções gerais de consulta. Aqui está uma lista completa de opções . Por exemplo, você pode definir limit e skip no terceiro argumento.

const res = await Customer.find({}, null, { sort: { name: 1 }, limit: 1 });
res[0].name; // 'A'

Perceba que o Model.find() tem uma assinatura de função diferente do que a função collection.find() do driver do MongoDB. O driver do MongoDB leva apenas 2 argumentos, filter e options. Para converter uma chamada find() de driver do MongoDB em uma chamada Model.find() do Mongoose, sem encadeamento, adicione null como o segundo argumento.

// consulta com driver do MongoDB
client.db().collection('customers').find({ email: /foo\.bar/ }, { limit: 1 });
// Equivalente em Mongoose
Customer.find({ email: /foo\.bar/ }, null, { limit: 1 });
// Equivalente em Mongoose usando encadeamento
Customer.find({ email: /foo\.bar/ }).limit(1);

Promises e Async/Await

Customer.find({ name: 'A' }).
then(customers => {
console.log(customers[0].name); // 'A'
return Customer.find({ name: 'B' });
}).
then(customers => {
console.log(customers[0].name); // 'B'
});

As consultas também têm uma função .catch(). Em geral, um thenablenão precisa ter uma função .catch(), mas o Mongoose adicionou uma para sua conveniência. Abaixo está um exemplo de .catch() e como manipular um número incorreto na sua consulta.

Customer.find({ age: 'not a number' }).
catch(err => console.log('Caught:', err.message));
// Caught: Cast to number failed for value "not a number" at path "age" for model "Customer"

As consultas são editáveis, mas as consultas não são Promises. Em alguns casos, você pode precisar de uma Promise, não apenas de um thenable. Por exemplo, você pode estar usando TypeScript em modo strict ou pode estar usando o módulo cls-hooked. A função Query#exec() retorna uma Promise completa.

const q = Customer.find();
q instanceof Promise; // false
q.exec() instanceof Promise; // true

Continuando

Créditos

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store