Mongoose: Como find() funciona
Tags: ptbr, webdev, mongodb, database
Published: 2019-10-12 09:27:40

Há [alguma confusão](https://stackoverflow.com/questions/15852043/mongoose-find-how-to-access-the-result-documents) 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](https://thecodebarbarian.com/write-your-own-node-js-promise-library-from-scratch.html). Neste artigo, fornecerei uma visão geral conceitual do que acontece quando você chama `Model.find()` para poder responder a perguntas semelhantes.

# Configuração

Para os fins deste artigo, presumo que você já tenha uma instância do MongoDB em execução no `localhost:27017`. Caso contrário, confira o [run-rs](https://www.npmjs.com/package/run-rs), ele baixa e executa o MongoDB para você, sem dependências além do Node.js. Aqui está um script independente que demonstra a criação de alguns documentos e o uso de `.find()`:

```js
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

O Mongoose realmente tem duas funções `find()`. O exemplo acima usa `Model.find()`, mas também existe o `Query#find()`. O `Query#find()` é abreviado para `Query.prototype.find()`, o `.find()` é um método de instância da [classe Query](https://mongoosejs.com/docs/queries.html).

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](https://schier.co/blog/2013/11/14/method-chaining-in-javascript.html) para criar consultas mais sofisticadas. Você não instancia uma `Query` diretamente, o `Customer.find()` instancia um para você.

```js
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**.

```js
// 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](https://mongoosejs.com/docs/api.html#query_Query) para criar operações sofisticadas de CRUD. Os mais utilizados são `Query#sort()`, `Query#limit()` e `Query#skip()`.

```js
// 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](https://mongoosejs.com/docs/guide.html) do modelo. Isso significa que você não precisa converter explicitamente cadeias de caracteres para [ObjectIds](https://docs.mongodb.com/manual/reference/method/ObjectId/) ou se preocupar com as [nuances de converter cadeias de caracteres em números](http://thecodebarbarian.com/convert-a-string-to-a-number-in-javascript.html).

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

Os ajudantes `sort()`, `limit()` e `skip()` modificam as [opções de consulta](https://mongoosejs.com/docs/api.html#query_Query-getOptions). Por exemplo, o `query.getOptions()` abaixo retornará um objeto que contém `sort` e `limit` propriedades.

```js
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](https://docs.mongodb.com/manual/reference/operator/projection/positional/) da [consulta](https://docs.mongodb.com/manual/reference/operator/projection/positional/), 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.

```js
// 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.

```js
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](http://mongodb.github.io/node-mongodb-native/3.1/api/Collection.html#find). 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.

```js
// 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](https://asyncawait.net/)

`Model.find()` retorna uma instância de consulta, então por que você pode fazer `await Model.find()`? Isso ocorre porque uma consulta do Mongoose é um [**thenable**](https://developers.google.com/web/fundamentals/primers/promises#promise-terminology), o que significa que eles têm uma função `.then()`. Isso significa que você pode usar as consultas da mesma maneira que **Promises**, inclusive com o encadeamento de delas, como mostrado abaixo.

```js
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 **thenable** nã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.

```js
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](https://mongoosejs.com/docs/queries.html#queries-are-not-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](https://github.com/Automattic/mongoose/issues/7292). A função `Query#exec()` retorna uma Promise completa.

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

# Continuando

Encontrar todos os documentos que correspondem a uma consulta no Mongoose é intuitivo, mas há nuances que aparecem quando você vai além das consultas mais básicas. O Mongoose permite estruturar consultas usando encadeamento ou, equivalentemente, usando POJOs em uma única chamada de função. `Model.find()` retorna uma consulta, que possui um método `.find()` separado que permite anexar filtros adicionais. As consultas não são Promises, mas são próximas o suficiente para a maioria dos usos práticos. Lembre-se desses três conceitos e você saberá o suficiente para resolver os problemas mais comuns do Mongoose.

# Créditos

- [How find() Works in Mongoose](http://thecodebarbarian.com/how-find-works-in-mongoose), escrito originalmente por [Valeri Karpov](http://thecodebarbarian.com/how-find-works-in-mongoose)