Puede consultar datos almacenados en MongoDB Atlas directamente desde el código de su aplicación cliente utilizando el SDK de Realm React Native. Cliente MongoDB con la API de consulta. Atlas App Services proporciona reglas de acceso a datos en colecciones para recuperar resultados de forma segura según el usuario conectado o el contenido de cada documento.
Nota
Conjunto de datos de ejemplo
Los ejemplos de esta página utilizan una colección de MongoDB que describe el inventario de una cadena de almacenes de plantas. Para obtener más información sobre el esquema de la colección y el contenido del documento, consulte Datos de ejemplo.
Casos de uso
Existen variedad de razones por las que podría querer query una fuente de datos de MongoDB. Trabajar con datos en tu cliente a través de Atlas Device Sync no siempre es práctico o posible. Podrías querer query MongoDB cuando:
El conjunto de datos es grande o el dispositivo cliente tiene restricciones para cargar todo el conjunto de datos
Estás recuperando documentos que no están modelados en Realm
Su aplicación necesita acceder a colecciones que no tienen esquemas estrictos
Un servicio que no es de Realm genera colecciones a las que desea acceder
Si bien no son exhaustivos, estos son algunos casos de uso comunes para consultar MongoDB directamente.
Requisitos previos
Antes de poder consultar MongoDB desde su aplicación React Native, debe configurar el acceso a datos de MongoDB en su aplicación de App Services. Para saber cómo configurar su aplicación backend para que el SDK de Realm pueda consultar Atlas, consulte "Configurar el acceso a datos de MongoDB" en la documentación de App Services.
Ejemplo
Datos de ejemplo
Los ejemplos de esta página utilizan la siguiente colección MongoDB que describe varias plantas a la venta en una cadena de tiendas de plantas:
{ _id: ObjectId("5f87976b7b800b285345a8c4"), name: "venus flytrap", sunlight: "full", color: "white", type: "perennial", _partition: "Store 42" }, { _id: ObjectId("5f87976b7b800b285345a8c5"), name: "sweet basil", sunlight: "partial", color: "green", type: "annual", _partition: "Store 42" }, { _id: ObjectId("5f87976b7b800b285345a8c6"), name: "thai basil", sunlight: "partial", color: "green", type: "perennial", _partition: "Store 42" }, { _id: ObjectId("5f87976b7b800b285345a8c7"), name: "helianthus", sunlight: "full", color: "yellow", type: "annual", _partition: "Store 42" }, { _id: ObjectId("5f87976b7b800b285345a8c8"), name: "petunia", sunlight: "full", color: "purple", type: "annual", _partition: "Store 47" }
Documentos en el plants La colección utiliza el siguiente esquema:
{ "title": "Plant", "bsonType": "object", "required": ["_id", "_partition", "name"], "properties": { "_id": { "bsonType": "objectId" }, "_partition": { "bsonType": "string" }, "name": { "bsonType": "string" }, "sunlight": { "bsonType": "string" }, "color": { "bsonType": "string" }, "type": { "bsonType": "string" } } }
type Plant = { _id: BSON.ObjectId; _partition: string; name: string; sunlight?: string; color?: string; type?: string; };
Conectarse a un clúster vinculado
Para acceder a un clúster vinculado desde su aplicación cliente, autentique a un usuario y pase el nombre del clúster a User.mongoClient(). Esto devuelve una interfaz de servicio MongoDB que puede usar para acceder a las bases de datos y colecciones del clúster.
Si está utilizando @realm/react, puede acceder al cliente MongoDB con el gancho useUser() en un componente envuelto por UserProvider.
import React from 'react'; import {useUser} from '@realm/react'; function QueryPlants() { // Get currently logged in user const user = useUser(); const getPlantByName = async name => { // Access linked MongoDB collection const mongodb = user.mongoClient('mongodb-atlas'); const plants = mongodb.db('example').collection('plants'); // Query the collection const response = await plants.findOne({name}); return response; }; // ... }
import React from 'react'; import {useUser} from '@realm/react'; function QueryPlants() { // Get currently logged in user const user = useUser(); const getPlantByName = async (name: string) => { // Access linked MongoDB collection const mongodb = user.mongoClient('mongodb-atlas'); const plants = mongodb.db('example').collection<Plant>('plants'); // Query the collection const response = await plants.findOne({name}); return response; }; // ... }
Operaciones de lectura
Buscar un solo documento
Para encontrar un solo documento, envíe una consulta que coincida con el documento a collection.findOne(). Si no envía ninguna consulta, findOne() coincide con el primer documento que encuentre en la colección.
El siguiente fragmento encuentra el documento que describe las plantas "Venus atrapamoscas" en la colección de documentos que describen plantas a la venta en un grupo de tiendas:
const venusFlytrap = await plants.findOne({ name: "venus flytrap" }); console.log("venusFlytrap", venusFlytrap);
{ _id: ObjectId("5f87976b7b800b285345a8c4"), name: "venus flytrap", sunlight: "full", color: "white", type: "perennial", _partition: "Store 42", }
Encuentra varios documentos
Para encontrar varios documentos, envíe una consulta que coincida con los documentos a collection.find(). Si no envía ninguna consulta, find() coincide con todos los documentos de la colección.
El siguiente fragmento encuentra todos los documentos que describen plantas perennes en la colección de documentos que describen plantas en venta en un grupo de tiendas:
const perennials = await plants.find({ type: "perennial" }); console.log("perennials", perennials);
[ { _id: ObjectId("5f87976b7b800b285345a8c4"), name: 'venus flytrap', sunlight: 'full', color: 'white', type: 'perennial', _partition: 'Store 42' }, { _id: ObjectId("5f87976b7b800b285345a8c6"), name: 'thai basil', sunlight: 'partial', color: 'green', type: 'perennial', _partition: 'Store 42' }, { _id: ObjectId("5f879f83fc9013565c23360e"), name: 'lily of the valley', sunlight: 'full', color: 'white', type: 'perennial', _partition: 'Store 47' }, { _id: ObjectId("5f87a0defc9013565c233611"), name: 'rhubarb', sunlight: 'full', color: 'red', type: 'perennial', _partition: 'Store 47' }, { _id: ObjectId("5f87a0dffc9013565c233612"), name: 'wisteria lilac', sunlight: 'partial', color: 'purple', type: 'perennial', _partition: 'Store 42' }, { _id: ObjectId("5f87a0dffc9013565c233613"), name: 'daffodil', sunlight: 'full', color: 'yellow', type: 'perennial', _partition: 'Store 42' } ]
Contabilizar documentos
Para contar documentos, pase una consulta que coincida con los documentos a collection.count(). Si no pasa ninguna consulta, count() cuenta todos los documentos de la colección.
El siguiente fragmento cuenta la cantidad de documentos en una colección de documentos que describen plantas en venta en un grupo de tiendas:
const numPlants = await plants.count(); console.log(`There are ${numPlants} plants in the collection`);
"There are 9 plants in the collection"
Operaciones de escritura
Inserta un solo documento
Para insertar un solo documento, páselo a collection.insertOne().
El siguiente fragmento inserta un único documento que describe una planta de "lirio de los valles" en una colección de documentos que describen plantas a la venta en un grupo de tiendas:
const result = await plants.insertOne({ name: "lily of the valley", sunlight: "full", color: "white", type: "perennial", _partition: "Store 47", }); console.log(result);
{ insertedId: "5f879f83fc9013565c23360e", }
Inserta varios documentos
Para insertar varios documentos al mismo tiempo, páselos como una matriz a collection.insertMany().
El siguiente fragmento inserta tres documentos que describen plantas en una colección de documentos que describen plantas en venta en un grupo de tiendas:
const result = await plants.insertMany([ { name: "rhubarb", sunlight: "full", color: "red", type: "perennial", _partition: "Store 47", }, { name: "wisteria lilac", sunlight: "partial", color: "purple", type: "perennial", _partition: "Store 42", }, { name: "daffodil", sunlight: "full", color: "yellow", type: "perennial", _partition: "Store 42", }, ]); console.log(result);
{ insertedIds: [ "5f87a0defc9013565c233611", "5f87a0dffc9013565c233612", "5f87a0dffc9013565c233613", ], }
Actualiza un solo documento
Para actualizar un solo documento, pase una consulta que coincida con el documento y un documento de actualización a collection.updateOne().
El siguiente fragmento actualiza un documento de una colección que describe plantas a la venta en un grupo de tiendas. Esta operación busca un documento cuyo name campo contiene el valor "petunia" y cambia el valor del campo del primer documento coincidente sunlight a "parcial":
const result = await plants.updateOne( { name: "petunia" }, { $set: { sunlight: "partial" } } ); console.log(result);
{ matchedCount: 1, modifiedCount: 1 }
Actualiza varios documentos
Para actualizar varios documentos al mismo tiempo, pasar una query que coincida con los documentos y una descripción de actualizar a colección.updateMany().
El siguiente fragmento actualiza varios documentos de una colección que describen plantas en venta en un grupo de tiendas. Esta operación busca documentos cuyo _partition campo contenga el valor "Tienda 47" y cambia el valor del _partition campo de cada documento coincidente a "Tienda 51":
const result = await plants.updateMany( { _partition: "Store 47" }, { $set: { _partition: "Store 51" } } ); console.log(result);
{ matchedCount: 3, modifiedCount: 3 }
Insertar documentos
Para hacer una inserción de un documento, configure la opción upsert en true en su operación de actualización. Si la query de la operación no coincide con ningún documento en la colección, una inserción inserta automáticamente un nuevo documento único en la colección que coincide con el documento de query proporcionado con la actualización aplicada a este.
El siguiente fragmento actualiza un documento de una colección que describe plantas en venta en un grupo de tiendas mediante una operación upsert. La consulta no coincide con ningún documento existente, por lo que MongoDB crea uno nuevo automáticamente.
const result = await plants.updateOne( { sunlight: "full", type: "perennial", color: "green", _partition: "Store 47", }, { $set: { name: "sweet basil" } }, { upsert: true } ); console.log(result);
{ matchedCount: 0, modifiedCount: 0, upsertedId: ObjectId("5f1f63055512f2cb67f460a3"), }
Borrar un único documento
Para eliminar un solo documento de una colección, pase una consulta que coincida con el documento a collection.deleteOne(). Si no pasa una consulta o si esta coincide con varios documentos, la operación elimina el primer documento que encuentra.
El siguiente fragmento elimina un documento de una colección que describe plantas en venta en un grupo de tiendas. Esta operación busca un documento cuyo color campo tenga el valor "verde" y elimina el primer documento que coincida con la consulta:
const result = await plants.deleteOne({ color: "green" }); console.log(result);
{ deletedCount: 1 }
Borra varios documentos
Para eliminar varios documentos de una colección, pase una consulta que coincida con los documentos a collection.deleteMany(). Si no pasa ninguna consulta, deleteMany() elimina todos los documentos de la colección.
El siguiente fragmento elimina todos los documentos de plantas que se encuentran en "Tienda 51" en una colección de documentos que describen plantas en venta en un grupo de tiendas:
const result = await plants.deleteMany({ _partition: "Store 51", }); console.log(result);
{ deletedCount: 3 }
Notificaciones de cambios en tiempo real
Puedes llamar a collection.watch() para suscribirte a las notificaciones de cambios en tiempo real que MongoDB emite cada vez que se añade, modifica o elimina un documento de la colección. Cada notificación especifica el documento modificado, cómo se modificó y el documento completo después de la operación que provocó el evento.
collection.watch() devuelve un generador asíncronoque le permite extraer de forma asincrónica eventos de cambio para las operaciones a medida que ocurren.
collection.watch() Requiere cierta configuración para funcionar con una aplicación cliente de React Native. Para supervisar los cambios en una colección, primero debe instalar los paquetes react-native-polyfill-globals y @babel/plugin-proposal-async-generator-functions.
Para utilizar collection.watch():
Instalar dependencias.
npm install react-native-polyfill-globals text-encoding npm install --save-dev @babel/plugin-proposal-async-generator-functions Importa polyfills en un ámbito superior al necesario. Por ejemplo, en
index.js.import { polyfill as polyfillReadableStream } from "react-native-polyfill-globals/src/readable-stream"; import { polyfill as polyfillEncoding } from "react-native-polyfill-globals/src/encoding"; import { polyfill as polyfillFetch } from "react-native-polyfill-globals/src/fetch"; polyfillReadableStream(); polyfillEncoding(); polyfillFetch();
Importante
Limitaciones de la tecnología sin servidor
No puedes observar cambios si la fuente de datos es una instancia sin servidor de Atlas. El servidor sin servidor de MongoDB actualmente no admite flujos de cambios, que se emplean en las colecciones monitoreadas para escuchar cambios.
Esté atento a todos los cambios en una colección
Con el @realm/react paquete, asegúrese de haber configurado la autenticación de usuario para su aplicación.
Para supervisar todos los cambios en una colección, llame a collection.watch() sin argumentos. Esta llamada debe estar encapsulada por un UserProvider con un usuario autenticado.
import React, {useEffect} from 'react'; import Realm from 'realm'; import {useUser, useApp, AppProvider, UserProvider} from '@realm/react'; function AppWrapper() { return ( <AppProvider id={APP_ID}> <UserProvider fallback={<LogIn />}> <NotificationSetter /> </UserProvider> </AppProvider> ); } function NotificationSetter() { // Get currently logged in user const user = useUser(); const watchForAllChanges = async ( plants, ) => { // Watch for changes to the plants collection for await (const change of plants.watch()) { switch (change.operationType) { case 'insert': { const {documentKey, fullDocument} = change; // ... do something with the change information. break; } case 'update': { const {documentKey, fullDocument} = change; // ... do something with the change information. break; } case 'replace': { const {documentKey, fullDocument} = change; // ... do something with the change information. break; } case 'delete': { const {documentKey} = change; // ... do something with the change information. break; } } } }; useEffect(() => { const plants = user .mongoClient('mongodb-atlas') .db('example') .collection('plants'); // Set up notifications watchForAllChanges(plants); }, [user, watchForAllChanges]); // ... rest of component }
import React, {useEffect} from 'react'; import Realm from 'realm'; import {useUser, useApp, AppProvider, UserProvider} from '@realm/react'; function AppWrapper() { return ( <AppProvider id={APP_ID}> <UserProvider fallback={<LogIn />}> <NotificationSetter /> </UserProvider> </AppProvider> ); } function NotificationSetter() { // Get currently logged in user const user = useUser(); const watchForAllChanges = async ( plants: Realm.Services.MongoDB.MongoDBCollection<Plant>, ) => { // Watch for changes to the plants collection for await (const change of plants.watch()) { switch (change.operationType) { case 'insert': { const {documentKey, fullDocument} = change; // ... do something with the change information. break; } case 'update': { const {documentKey, fullDocument} = change; // ... do something with the change information. break; } case 'replace': { const {documentKey, fullDocument} = change; // ... do something with the change information. break; } case 'delete': { const {documentKey} = change; // ... do something with the change information. break; } } } }; useEffect(() => { const plants = user! .mongoClient('mongodb-atlas') .db('example') .collection<Plant>('plants'); // Set up notifications watchForAllChanges(plants); }, [user, watchForAllChanges]); // ... rest of component }
Esté atento a cambios específicos en una colección
Para observar cambios específicos en una colección, pase una consulta que coincida con los campos de eventos de cambio a collection.watch():
for await (const change of plants.watch({ filter: { operationType: "insert", "fullDocument.type": "perennial", }, })) { // The change event will always represent a newly inserted perennial const { documentKey, fullDocument } = change; console.log(`new document: ${documentKey}`, fullDocument); }
Operaciones de agregación
Las operaciones de agregación ejecutan todos los documentos de una colección a través de una serie de etapas denominadas canalización de agregación. La agregación permite filtrar y transformar documentos, recopilar datos resumidos sobre grupos de documentos relacionados y realizar otras operaciones complejas con datos.
Ejecuta una pipeline de agregación
Para ejecutar una canalización de agregación, pase una matriz de etapas de agregación a collection.aggregate(). Las operaciones de agregación devuelven el conjunto de resultados de la última etapa de la canalización.
El siguiente fragmento agrupa todos los documentos de la colección plants por su valor type y agrega un recuento de la cantidad de cada tipo:
const result = await plants.aggregate([ { $group: { _id: "$type", total: { $sum: 1 }, }, }, { $sort: { _id: 1 } }, ]); console.log(result);
[ { _id: "annual", total: 1 }, { _id: "perennial", total: 5 }, ]
Filtrar Documentos
Puede utilizar la etapa $match para filtrar documentos según la sintaxis de consulta estándar de MongoDB.
{ "$match": { "<Field Name>": <Query Expression>, ... } }
Ejemplo
El siguiente filtro de etapa $match filtra documentos para incluir solo aquellos donde el campo type tenga un valor igual a "perenne":
const perennials = await plants.aggregate([ { $match: { type: { $eq: "perennial" } } }, ]); console.log(perennials);
[ { "_id": ObjectId("5f87976b7b800b285345a8c4"), "_partition": "Store 42", "color": "white", "name": "venus flytrap", "sunlight": "full", "type": "perennial" }, { "_id": ObjectId("5f87976b7b800b285345a8c6"), "_partition": "Store 42", "color": "green", "name": "thai basil", "sunlight": "partial", "type": "perennial" }, { "_id": ObjectId("5f87a0dffc9013565c233612"), "_partition": "Store 42", "color": "purple", "name": "wisteria lilac", "sunlight": "partial", "type": "perennial" }, { "_id": ObjectId("5f87a0dffc9013565c233613"), "_partition": "Store 42", "color": "yellow", "name": "daffodil", "sunlight": "full", "type": "perennial" }, { "_id": ObjectId("5f1f63055512f2cb67f460a3"), "_partition": "Store 47", "color": "green", "name": "sweet basil", "sunlight": "full", "type": "perennial" } ]
Documentos de grupo
Puede usar la etapa $group para agregar datos de resumen de uno o más documentos. MongoDB agrupa documentos según la expresión definida en el _id campo de la $group etapa. Puede hacer referencia a un campo de documento específico anteponiendo al nombre del $ campo.
{ "$group": { "_id": <Group By Expression>, "<Field Name>": <Aggregation Expression>, ... } }
Ejemplo
La siguiente etapa $group organiza los documentos por valor de su campo type y calcula la cantidad de documentos de plantas en los que cada valor único type aparece.
const result = await plants.aggregate([ { $group: { _id: "$type", numItems: { $sum: 1 }, }, }, { $sort: { _id: 1 } }, ]); console.log(result);
[ { _id: "annual", numItems: 1 }, { _id: "perennial", numItems: 5 }, ]
Paginar documentos
Para paginar resultados, puede usar consultas de agregación de rangos con los $match $sortoperadores,$limit y. Para obtener más información sobre la paginación de documentos, consulte "Uso de consultas de rangos" en la documentación de MongoDB Server.
Ejemplo
El siguiente ejemplo pagina una colección de documentos en orden ascendente.
// Paginates through list of plants // in ascending order by plant name (A -> Z) async function paginateCollectionAscending( collection, nPerPage, startValue ) { const pipeline = [{ $sort: { name: 1 } }, { $limit: nPerPage }]; // If not starting from the beginning of the collection, // only match documents greater than the previous greatest value. if (startValue !== undefined) { pipeline.unshift({ $match: { name: { $gt: startValue }, }, }); } const results = await collection.aggregate(pipeline); return results; } // Number of results to show on each page const resultsPerPage = 3; const pageOneResults = await paginateCollectionAscending( plants, resultsPerPage ); const pageTwoStartValue = pageOneResults[pageOneResults.length - 1].name; const pageTwoResults = await paginateCollectionAscending( plants, resultsPerPage, pageTwoStartValue ); // ... can keep paginating for as many plants as there are in the collection
Campos del documento del proyecto
Puede usar la etapa $project para incluir u omitir campos específicos de los documentos o para calcular nuevos campos mediante operadores de agregación. Las proyecciones funcionan de dos maneras:
Incluir explícitamente los campos con el valor 1. Esto tiene el efecto secundario de excluir implícitamente todos los campos no especificados.
Excluye implícitamente los campos con un valor de 0. Esto tiene el efecto secundario de incluir implícitamente todos los campos no especificados.
Estos dos métodos de proyección son mutuamente excluyentes: si incluye campos explícitamente, no puede excluirlos explícitamente, y viceversa.
Nota
El _id campo es un caso especial: siempre se incluye en todas las consultas, a menos que se especifique lo contrario. Por esta razón, se puede excluir el _id campo con un 0 valor e incluir simultáneamente otros campos,_partition como, con un 1 valor. Solo el caso especial de exclusión del _id campo permite tanto la exclusión como la inclusión en una sola $project etapa.
{ "$project": { "<Field Name>": <0 | 1 | Expression>, ... } }
Ejemplo
La siguiente etapa $project omite el campo _id, incluye el campo name y crea un nuevo campo llamado storeNumber. El storeNumber se genera mediante dos operadores de agregación:
$splitSepara el valor_partitionen dos segmentos de cadena que rodean el espacio. Por ejemplo, el valor "Store 42" dividido de esta manera devuelve una matriz con dos elementos: "Store" y "42".$arrayElemAtSelecciona un elemento específico de un array según el segundo argumento. En este caso, el valor1selecciona el segundo elemento del array generado por el operador$split, ya que los arrays indexan desde0. Por ejemplo, el valor ["Store", "42"] pasado a esta operación devolvería un valor de "42".
const result = await plants.aggregate([ { $project: { _id: 0, name: 1, storeNumber: { $arrayElemAt: [{ $split: ["$_partition", " "] }, 1], }, }, }, ]); console.log(result);
[ { "name": "venus flytrap", "storeNumber": "42" }, { "name": "thai basil", "storeNumber": "42" }, { "name": "helianthus", "storeNumber": "42" }, { "name": "wisteria lilac", "storeNumber": "42" }, { "name": "daffodil", "storeNumber": "42" }, { "name": "sweet basil", "storeNumber": "47" } ]
Agregar campos a los documentos
Puede utilizar la etapa $addFields para agregar nuevos campos con valores calculados utilizando operadores de agregación.
{ $addFields: { <newField>: <expression>, ... } }
Nota
$addFields es similar a $project pero no permite incluir u omitir campos.
Ejemplo
La siguiente etapa $addFields crea un nuevo campo llamado storeNumber donde el valor es la salida de dos operadores agregados que transforman el valor del campo _partition.
const result = await plants.aggregate([ { $addFields: { storeNumber: { $arrayElemAt: [{ $split: ["$_partition", " "] }, 1], }, }, }, ]); console.log(result);
[ { "_id": ObjectId("5f87976b7b800b285345a8c4"), "_partition": "Store 42", "color": "white", "name": "venus flytrap", "storeNumber": "42", "sunlight": "full", "type": "perennial" }, { "_id": ObjectId("5f87976b7b800b285345a8c6"), "_partition": "Store 42", "color": "green", "name": "thai basil", "storeNumber": "42", "sunlight": "partial", "type": "perennial" }, { "_id": ObjectId("5f87976b7b800b285345a8c7"), "_partition": "Store 42", "color": "yellow", "name": "helianthus", "storeNumber": "42", "sunlight": "full", "type": "annual" }, { "_id": ObjectId("5f87a0dffc9013565c233612"), "_partition": "Store 42", "color": "purple", "name": "wisteria lilac", "storeNumber": "42", "sunlight": "partial", "type": "perennial" }, { "_id": ObjectId("5f87a0dffc9013565c233613"), "_partition": "Store 42", "color": "yellow", "name": "daffodil", "storeNumber": "42", "sunlight": "full", "type": "perennial" }, { "_id": ObjectId("5f1f63055512f2cb67f460a3"), "_partition": "Store 47", "color": "green", "name": "sweet basil", "storeNumber": "47", "sunlight": "full", "type": "perennial" } ]
Unwind Array Values
Puede usar la etapa $unwind para transformar un documento que contiene una matriz en varios documentos que contienen valores individuales de esa matriz. Al desenrollar un campo de matriz, MongoDB copia cada documento una vez por cada elemento de dicho campo, pero reemplaza el valor de la matriz con el elemento de la matriz en cada copia.
{ $unwind: { path: <Array Field Path>, includeArrayIndex: <string>, preserveNullAndEmptyArrays: <boolean> } }
Ejemplo
El siguiente ejemplo utiliza la etapa $unwind para la combinación type y color de cada objeto. La canalización de agregación consta de los siguientes pasos:
Utilice la etapa
$groupcon$addToSetpara crear nuevos documentos para cadatypecon un nuevo campocolorsque contenga una matriz de todos los colores para ese tipo de flor que aparecen en la colección.Utilice la etapa
$unwindpara crear documentos separados para cada combinación de tipo y color.Utilice la etapa
$sortpara ordenar los resultados en orden alfabético.
const result = await plants.aggregate([ { $group: { _id: "$type", colors: { $addToSet: "$color" } } }, { $unwind: { path: "$colors" } }, { $sort: { _id: 1, colors: 1 } }, ]); console.log(result);
[ { "_id": "annual", "colors": "yellow" }, { "_id": "perennial", "colors": "green" }, { "_id": "perennial", "colors": "purple" }, { "_id": "perennial", "colors": "white" }, { "_id": "perennial", "colors": "yellow" }, ]