Spanish (Español) translation by Elías Nicolás (you can also view the original English article)
Mongoose es un marco de JavaScript que se usa comúnmente en una aplicación Node.js con una base de datos MongoDB. En este artículo, voy a presentarle Mongoose y MongoDB, y más importante aún, dónde estas tecnologías se adaptan a su aplicación.
¿Qué es MongoDB?
Comencemos con MongoDB. MongoDB es una base de datos que almacena sus datos como documentos. Lo más común es que estos documentos se parezcan a una estructura similar a JSON:
{ firstName: "Jamie", lastName: "Munro" }
Un documento luego se coloca dentro de una colección. Como ejemplo, el ejemplo de documento anterior define un objeto user
. Este objeto user
normalmente sería parte de una colección llamada users
.
Uno de los factores clave con MongoDB es su flexibilidad cuando se trata
de estructura. Aunque
en el primer ejemplo, el objeto user
contenía una propiedad
firstName
y lastName
, estas propiedades no son necesarias en todos los
documentos user
que forman parte de la colección users
. Esto
es lo que hace que MongoDB sea muy diferente de una base de datos
SQL como MySQL o Microsoft SQL Server que requiere un esquema de base
de datos fuertemente definido de cada objeto que almacena.
La capacidad de crear objetos dinámicos que se almacenan como documentos en la base de datos es donde Mongoose entra en juego.
¿Qué es la Mongoose?
Mangosta es un Object Document Mapper (ODM). Esto significa que Mongoose le permite definir objetos con un esquema fuertemente tipado que se asigna a un documento MongoDB.
Mongoose proporciona una increíble cantidad de funcionalidades para crear y trabajar con esquemas. Mongoose actualmente contiene ocho SchemaTypes que una propiedad se guarda como cuando se conserva a MongoDB. Son:
- String (Cadena)
- Number (Número)
- Date (Fecha)
- Buffer
- Boolean (Booleano)
- Mixed (Mixto)
- ObjectId
- Array (Matriz)
Cada tipo de datos le permite especificar:
- un valor predeterminado
- una función de validación personalizada
- indica que se requiere un campo
- una función get que le permite manipular los datos antes de que se devuelva como un objeto
- una función de conjunto que le permite manipular los datos antes de guardarlos en la base de datos
- crear índices para permitir que los datos se obtengan más rápido
Además
de estas opciones comunes, ciertos tipos de datos le permiten
personalizar aún más cómo se almacenan y recuperan los datos de la base
de datos. Por ejemplo, un tipo de datos String
también le permite
especificar las siguientes opciones adicionales:
- convertirlo a minúsculas
- convertirlo a mayúsculas
- recortar datos antes de guardar
- una expresión regular que puede limitar los datos que se pueden guardar durante el proceso de validación
- una enumeración que puede definir una lista de cadenas que son válidas
Las propiedades Número Number
y Fecha Date
son compatibles con la especificación de un valor mínimo y
máximo permitido para ese campo.
La mayoría de los ocho tipos de datos
permitidos deberían serle familiares. Sin embargo, hay varias
excepciones que pueden surgirle, como Buffer
, Mixed
, ObjectId
y Array
.
El
tipo de datos Buffer
le permite guardar datos binarios. Un ejemplo
común de datos binarios sería una imagen o un archivo codificado, como
un documento PDF.
El tipo de datos Mixed
convierte la propiedad en un
campo "todo vale". Este campo se parece a cuántos desarrolladores pueden
usar MongoDB porque no hay una estructura definida. Tenga cuidado con
el uso de este tipo de datos ya que pierde muchas de
las excelentes funciones que ofrece Mongoose, como la validación de
datos y la detección de cambios de entidades para saber automáticamente
si desea actualizar la propiedad al guardar.
El tipo de datos ObjectId
comúnmente especifica un enlace a otro documento en su base de datos.
Por ejemplo, si tiene una colección de libros y autores, el documento
del libro puede contener una propiedad ObjectId
que hace referencia al
autor específico del documento.
El tipo de datos Array
le permite
almacenar matrices similares a JavaScript. Con un tipo de datos Array,
puede realizar operaciones de matriz JavaScript comunes en ellos, como
push, pop, shift, slice, etc.
Recapitulación rápida
Antes de continuar y generar algún código, solo quería recapitular lo que acabamos de aprender. MongoDB es una base de datos que le permite almacenar documentos con una estructura dinámica. Estos documentos se guardan dentro de una colección.
Mongoose es una biblioteca de JavaScript que le permite definir esquemas con datos fuertemente tipados. Una vez que se define un esquema, Mongoose le permite crear un Modelo basado en un esquema específico. Un modelo de mangosta se asigna a un documento MongoDB a través de la definición del esquema del modelo.
Una vez que haya definido sus esquemas y modelos, Mongoose contiene muchas funciones diferentes que le permiten validar, guardar, eliminar y consultar sus datos utilizando las funciones comunes de MongoDB. Hablaré de esto más con los ejemplos concretos del código a seguir.
Instalando MongoDB
Antes de que podamos comenzar a crear nuestros esquemas y modelos de Mongoose, debe instalarse y configurarse MongoDB. Sugeriría visitar la página de descarga de MongoDB. Hay varias opciones diferentes disponibles para instalar. Me he vinculado al servidor de la comunidad. Esto le permite instalar una versión específica para su sistema operativo. MongoDB también ofrece un Enterprise Server y una instalación de soporte en la nube. Dado que se pueden escribir libros completos sobre la instalación, el ajuste y la supervisión de MongoDB, me quedaré con el Community Server.
Una vez que haya descargado e instalado MongoDB para su sistema operativo de su elección, tendrá que iniciar la base de datos. En lugar de reinventar la rueda, sugeriría visitar la documentación de MongoDB sobre cómo instalar MongoDB Community Edition.
Voy a esperar aquí mientras configura MongoDB. Cuando esté listo, podemos pasar a la configuración de Mongoose para conectarse a su base de datos MongoDB recién instalada.
Configuración de Mongoose
Mongoose es un marco de JavaScript, y voy a usarlo en una aplicación Node.js. Si ya tiene Node.js instalado, puede pasar al siguiente paso. Si no tiene instalado Node.js, le sugiero que comience visitando la página de descarga de Node.js y seleccionando el instalador para su sistema operativo.
Con Node.js configurado y listo, voy a crear una nueva aplicación y luego instalar el paquete Mongoose NPM.
Con un símbolo del sistema que está configurado en donde desea instalar su aplicación, puede ejecutar los siguientes comandos:
mkdir mongoose_basics cd mongoose_basics npm init
Para la inicialización de mi aplicación, dejé todo como sus valores predeterminados. Ahora voy a instalar el paquete de mangosta de la siguiente manera:
npm install mongoose --save
Con todos los requisitos previos configurados, conéctese a una base de datos MongoDB. Coloqué el siguiente código dentro de un archivo index.js porque lo elegí como punto de partida para mi aplicación:
var mongoose = require('mongoose'); mongoose.connect('mongodb://localhost/mongoose_basics');
La primera línea de código incluye la biblioteca mongoose
. A continuación, abro una conexión a una base de datos a la que he llamado mongoose_basics
utilizando la función connect
.
La función connect
acepta otros dos parámetros opcionales. El
segundo parámetro es un objeto de opciones donde puede definir cosas
como el nombre de usuario y la contraseña, si es necesario. El
tercer parámetro, que también puede ser el segundo parámetro si no
tiene opciones, es la función de devolución de llamada después de
intentar conectarse. La función de devolución de llamada se puede usar de una de estas dos formas:
mongoose.connect(uri, options, function(error) { // Check error in initial connection. There is no 2nd param to the callback. }); // Or using promises mongoose.connect(uri, options).then( () => { /** ready to use. The `mongoose.connect()` promise resolves to undefined. */ }, err => { /** handle initial connection error */ } );
Para evitar una posible introducción a las promesas de JavaScript, usaré la primera. A continuación se muestra un archivo index.js actualizado:
var mongoose = require('mongoose'); mongoose.connect('mongodb://localhost/mongoose_basics', function (err) { if (err) throw err; console.log('Successfully connected'); });
Si se produce un error al conectarse a la base de datos, se lanza la excepción y se detiene todo el procesamiento posterior. Cuando no ocurre ningún error, he registrado un mensaje de éxito en la consola.
Mongoose ahora está configurado y conectado a una base de datos llamada
mongoose_basics
. Mi conexión MongoDB no usa nombre de usuario,
contraseña o puerto personalizado. Si
necesita establecer estas opciones o cualquier otra opción durante la
conexión, le sugiero que revise la documentación de Mongoose al
conectarse. La documentación proporciona
explicaciones detalladas de las muchas opciones disponibles, así como
también cómo crear conexiones múltiples, agrupación de conexiones,
réplicas, etc.
Con una conexión exitosa, sigamos adelante para definir un esquema de Mongoose.
Definiendo un Esquema de Mongoose
Durante la
introducción, mostré un objeto user
que contenía dos propiedades:
firstName
y lastName
. En el siguiente ejemplo, he traducido ese
documento en un esquema de Mongoose:
var userSchema = mongoose.Schema({ firstName: String, lastName: String });
Este es un esquema muy básico que solo contiene dos propiedades sin atributos asociados. Ampliemos este ejemplo convirtiendo las propiedades de nombre y apellido en objetos secundarios de una propiedad name
. La propiedad name
comprenderá tanto el nombre como el apellido.
También agregaré una propiedad created
que sea de tipo Date
.
var userSchema = mongoose.Schema({ name: { firstName: String, lastName: String }, created: Date });
Como puede ver, Mongoose me permite crear esquemas muy flexibles con muchas combinaciones diferentes posibles de cómo puedo organizar mis datos.
En
este próximo ejemplo, voy a crear dos nuevos esquemas que demostrarán
cómo crear una relación con otro esquema: author
y book
. El esquema book
contendrá una referencia al esquema author
.
var authorSchema = mongoose.Schema({ _id: mongoose.Schema.Types.ObjectId, name: { firstName: String, lastName: String }, biography: String, twitter: String, facebook: String, linkedin: String, profilePicture: Buffer, created: { type: Date, default: Date.now } });
Arriba está el esquema author
que amplía los conceptos del esquema user
que creé en el ejemplo anterior. Para
vincular el Autor y el Libro juntos, la primera propiedad del esquema
author
es una propiedad _id
que es un tipo de esquema ObjectId
. _id
es la sintaxis común para crear una clave primaria en Mongoose y
MongoDB. Luego, al igual que el esquema user
, he definido una
propiedad name
que contiene el nombre y apellido del
autor.
Ampliando el esquema user
, author
contiene varios otros
tipos de esquema String
. También agregué un tipo de esquema de Buffer
que podría contener la imagen de perfil del autor. La propiedad final
contiene la fecha de creación del autor; sin embargo, puede observar que
se crea de forma ligeramente diferente porque ha definido un valor
predeterminado de "ahora". Cuando un autor persiste en la base de datos,
esta propiedad se establecerá en la fecha / hora actual.
Para
completar los ejemplos de esquema, creamos un esquema book
que
contenga una referencia al autor utilizando el tipo de esquema ObjectId
:
var bookSchema = mongoose.Schema({ _id: mongoose.Schema.Types.ObjectId, title: String, summary: String, isbn: String, thumbnail: Buffer, author: { type: mongoose.Schema.Types.ObjectId, ref: 'Author' }, ratings: [ { summary: String, detail: String, numberOfStars: Number, created: { type: Date, default: Date.now } } ], created: { type: Date, default: Date.now } });
El esquema book
contiene varias propiedades de tipo String
. Como se mencionó anteriormente, contiene una referencia al esquema author
. Para demostrar aún más las potentes definiciones de esquema, el esquema
book
también contiene una Array
de ratings
. Cada
calificación consta de un summary
, detail
, numberOfStars
y una
propiedad created
.
Mongoose
le permite la flexibilidad de crear esquemas con referencias a otros
esquemas o, como en el ejemplo anterior con la propiedad ratings
, le permite crear una Array
de propiedades secundarias
que podría estar contenida en un esquema relacionado (como libro a
autor) o en línea como en el ejemplo anterior (con libro a una Array
de
calificaciones).
Crear y guardar modelos de Mongoose
Como
los esquemas author
y book
demuestran la flexibilidad del
esquema de Mongoose, voy a continuar usando esos esquemas y derivar un
modelo Author
y Book
de ellos.
var Author = mongoose.model('Author', authorSchema); var Book = mongoose.model('Book', bookSchema);
Un modelo de mangosta, cuando se guarda, crea un documento en MongoDB con las propiedades definidas por el esquema del que se deriva.
Para
demostrar la creación y el guardado de un objeto, en este próximo
ejemplo, crearé varios objetos: un Modelo de Author
y varios Modelos Book
. Una vez creados, estos objetos se conservarán en MongoDB utilizando el método save
del Modelo.
var jamieAuthor = new Author { _id: new mongoose.Types.ObjectId(), name: { firstName: 'Jamie', lastName: 'Munro' }, biography: 'Jamie is the author of ASP.NET MVC 5 with Bootstrap and Knockout.js.', twitter: 'https://twitter.com/endyourif', facebook: 'https://www.facebook.com/End-Your-If-194251957252562/' }; jamieAuthor.save(function(err) { if (err) throw err; console.log('Author successfully saved.'); var mvcBook = new Book { _id: new mongoose.Types.ObjectId(), title: 'ASP.NET MVC 5 with Bootstrap and Knockout.js', author: jamieAuthor._id, ratings:[{ summary: 'Great read' }] }; mvcBook.save(function(err) { if (err) throw err; console.log('Book successfully saved.'); }); var knockoutBook = new Book { _id: new mongoose.Types.ObjectId(), title: 'Knockout.js: Building Dynamic Client-Side Web Applications', author: jamieAuthor._id }; knockoutBook.save(function(err) { if (err) throw err; console.log('Book successfully saved.'); }); });
En el ejemplo anterior, he tapado descaradamente una referencia a mis dos libros más recientes. El ejemplo comienza creando y guardando un jamieObject
que se crea a partir de un Modelo Author
. Dentro de la función save
del jamieObject
, si ocurre un error, la
aplicación generará una excepción. Cuando el guardado es exitoso, dentro
de la función save
, los dos
objetos del libro se crean y guardan. . De forma similar al jamieObject
,
si se produce un error al guardar, se genera un error; de lo contrario,
se genera un mensaje de éxito en la consola.
Para crear la referencia al
Autor, los objetos del libro hacen
referencia a la clave primaria _id
del esquema author
en la propiedad
author
del esquema book
.
Validación de datos antes de guardar
Es bastante común que los datos que terminen creando un modelo se llenen por un formulario en una página web. Debido a esto, es una buena idea validar estos datos antes de guardar el Modelo en MongoDB.
En
el siguiente ejemplo, actualicé el esquema de autor anterior para
agregar validación en las siguientes propiedades: firstName
, twitter
,
facebook
y linkedin
var authorSchema = mongoose.Schema({ _id: mongoose.Schema.Types.ObjectId, name: { firstName: { type: String, required: true }, lastName: String }, biography: String, twitter: { type: String, validate: { validator: function(text) { return text.indexOf('https://twitter.com/') === 0; }, message: 'Twitter handle must start with https://twitter.com/' } }, facebook: { type: String, validate: { validator: function(text) { return text.indexOf('https://www.facebook.com/') === 0; }, message: 'Facebook must start with https://www.facebook.com/' } }, linkedin: { type: String, validate: { validator: function(text) { return text.indexOf('https://www.linkedin.com/') === 0; }, message: 'LinkedIn must start with https://www.linkedin.com/' } }, profilePicture: Buffer, created: { type: Date, default: Date.now } });
La propiedad firstName
ha sido atribuida con la propiedad required
. Ahora
cuando llamo a la función save
, Mongoose devolverá un error con
un mensaje que indica que se requiere la propiedad firstName
. Elegí no hacer la propiedad lastName
requerida en caso de que Cher o
Madonna fueran autores en mi base de datos.
Las propiedades twitter
,
facebook
, y linkedin
tienen validadores personalizados muy similares que
se les aplican. Cada uno de ellos garantiza que los valores comiencen
con el nombre de dominio respectivo de las redes sociales. Estos campos
no son necesarios, por lo que el validador solo se aplicará cuando se
proporcionen datos para esa propiedad.
Búsqueda y actualización de datos
Una introducción a Mongoose no estaría completa sin un ejemplo de buscar un registro y actualizar una o más propiedades en ese objeto.
Mongoose proporciona varias funciones diferentes para encontrar
datos para un modelo específico. Las funciones son find
, findOne
, y
findById
.
Las
funciones find
y findOne
aceptan un objeto como entrada para búsquedas
complejas, mientras que findById
acepta solo un valor con una función de
devolución de llamada (un ejemplo seguirá en breve). En este próximo
ejemplo, voy a demostrar cómo encontrar todos los libros que contienen
la cadena "mvc" en su título.
Book.find({ title: /mvc/i }).exec(function(err, books) { if (err) throw err; console.log(books); });
Dentro de
la función find
, estoy buscando la cadena "mvc" que no distingue
entre mayúsculas y minúsculas en la propiedad title
. Esto se logra usando la misma sintaxis para buscar una cadena con JavaScript.
La
función de búsqueda también se puede encadenar a otros métodos de
consulta, where
, and
, or
, limit
, sort
, any
, etc.
Ampliemos el ejemplo anterior para limitar nuestros resultados a los primeros cinco libros y ordenar en la fecha de creación descendente. Esto devolverá hasta los cinco libros más recientes que contengan "mvc" en el título.
Book.find({ title: /mvc/i }).sort('-created') .limit(5) .exec(function(err, books) { if (err) throw err; console.log(books); });
Después de
aplicar la función find
, el orden de las otras funciones no es
importante porque todas las funciones encadenadas se compilan juntas en
una sola consulta y no se ejecutan hasta que se llama a la función exec
.
Como mencioné anteriormente, findById
se ejecuta de forma un poco
diferente. Se ejecuta inmediatamente y acepta una función de devolución
de llamada, en lugar de permitir una cadena de funciones. En este
próximo ejemplo, estoy consultando a un autor específico por su _id
.
Author.findById('59b31406beefa1082819e72f', function(err, author) { if (err) throw err; console.log(author); });
El _id
en tu caso puede ser ligeramente diferente. Copié este _id
de un console.log
anterior al encontrar una lista de libros con "mvc" en su título.
Una vez que un objeto ha sido devuelto, puede modificar cualquiera de
sus propiedades para actualizarlo. Una vez que haya realizado los
cambios necesarios, llame al método save
, al igual que cuando
estaba creando el objeto. En este próximo ejemplo, extenderé el ejemplo
de findbyId
y actualizaré la propiedad linkedin
sobre el autor.
Author.findById('59b31406beefa1082819e72f', function(err, author) { if (err) throw err; author.linkedin = 'https://www.linkedin.com/in/jamie-munro-8064ba1a/'; author.save(function(err) { if (err) throw err; console.log('Author updated successfully'); }); });
Después de que el autor se recupera con éxito, se establece la propiedad linkedin
y se invoca la función save
. Mongoose
es capaz de detectar que la propiedad linkedin
se modificó y enviará
una declaración de actualización a MongoDB solo sobre las propiedades
que se han modificado. Si se produce un error al guardar, se lanzará una
excepción y detendrá la aplicación. Cuando tiene éxito, se registra un
mensaje de éxito en la consola.
Mongoose
también ofrece dos funciones adicionales que hacen que encontrar un
objeto y guardarlo en un solo paso con las funciones apropiadamente
nombradas: findByIdAndUpdate
y findOneAndUpdate
. Actualicemos el ejemplo
anterior para usar findByIdAndUpdate
.
Author.findByIdAndUpdate('59b31406beefa1082819e72f', { linkedin: 'https://www.linkedin.com/in/jamie-munro-8064ba1a/' }, function(err, author) { if (err) throw err; console.log(author); });
En el
ejemplo anterior, las propiedades para actualizar se suministran como un
objeto para el segundo parámetro de la función findByIdAndUpdate
. La función de devolución de llamada ahora es el tercer parámetro. Cuando la actualización es exitosa, el objeto author
devuelto contiene
la información actualizada. Esto se registra en la consola para ver las
propiedades del autor actualizado.
Código de muestra final
A lo largo de este artículo, proporcioné pequeños fragmentos de código que identifican una acción muy específica, como crear un esquema, crear un modelo, etc. Vamos a poner todo junto en un ejemplo completo.
En
primer lugar, he creado dos archivos adicionales: author.js
y book.js
.
Estos archivos contienen sus respectivas definiciones de esquema y la
creación del modelo. La última línea de código hace que el modelo esté
disponible para su uso en el archivo index.js
.
Comencemos con el archivo author.js:
var mongoose = require('mongoose'); var authorSchema = mongoose.Schema({ _id: mongoose.Schema.Types.ObjectId, name: { firstName: { type: String, required: true }, lastName: String }, biography: String, twitter: { type: String, validate: { validator: function(text) { return text.indexOf('https://twitter.com/') === 0; }, message: 'Twitter handle must start with https://twitter.com/' } }, facebook: { type: String, validate: { validator: function(text) { return text.indexOf('https://www.facebook.com/') === 0; }, message: 'Facebook must start with https://www.facebook.com/' } }, linkedin: { type: String, validate: { validator: function(text) { return text.indexOf('https://www.linkedin.com/') === 0; }, message: 'LinkedIn must start with https://www.linkedin.com/' } }, profilePicture: Buffer, created: { type: Date, default: Date.now } }); var Author = mongoose.model('Author', authorSchema); module.exports = Author;
Luego viene el archivo book.js
:
var mongoose = require('mongoose'); var bookSchema = mongoose.Schema({ _id: mongoose.Schema.Types.ObjectId, title: String, summary: String, isbn: String, thumbnail: Buffer, author: { type: mongoose.Schema.Types.ObjectId, ref: 'Author' }, ratings: [ { summary: String, detail: String, numberOfStars: Number, created: { type: Date, default: Date.now } } ], created: { type: Date, default: Date.now } }); var Book = mongoose.model('Book', bookSchema); module.exports = Book;
Y finalmente, el archivo index.js
actualizado:
var mongoose = require('mongoose'); var Author = require('./author'); var Book = require('./book'); mongoose.connect('mongodb://localhost/mongoose_basics', function (err) { if (err) throw err; console.log('Successfully connected'); var jamieAuthor = new Author({ _id: new mongoose.Types.ObjectId(), name: { firstName: 'Jamie', lastName: 'Munro' }, biography: 'Jamie is the author of ASP.NET MVC 5 with Bootstrap and Knockout.js.', twitter: 'https://twitter.com/endyourif', facebook: 'https://www.facebook.com/End-Your-If-194251957252562/' }); jamieAuthor.save(function(err) { if (err) throw err; console.log('Author successfully saved.'); var mvcBook = new Book({ _id: new mongoose.Types.ObjectId(), title: 'ASP.NET MVC 5 with Bootstrap and Knockout.js', author: jamieAuthor._id, ratings:[{ summary: 'Great read' }] }); mvcBook.save(function(err) { if (err) throw err; console.log('Book successfully saved.'); }); var knockoutBook = new Book({ _id: new mongoose.Types.ObjectId(), title: 'Knockout.js: Building Dynamic Client-Side Web Applications', author: jamieAuthor._id }); knockoutBook.save(function(err) { if (err) throw err; console.log('Book successfully saved.'); }); }); });
En el ejemplo anterior, todas las acciones de Mangosta están contenidas dentro de la función connect
. Los archivos author
y book
se incluyen con la función require
después de incluir la biblioteca mongoose
.
Con MongoDB en ejecución, ahora puede ejecutar la aplicación completa Node.js con el siguiente comando:
node index.js
Después de
guardar algunos datos en mi base de datos, actualicé el archivo index.js
con las funciones de búsqueda de la siguiente manera:
var mongoose = require('mongoose'); var Author = require('./author'); var Book = require('./book'); mongoose.connect('mongodb://localhost/mongoose_basics', function (err) { if (err) throw err; console.log('Successfully connected'); Book.find({ title: /mvc/i }).sort('-created') .limit(5) .exec(function(err, books) { if (err) throw err; console.log(books); }); Author.findById('59b31406beefa1082819e72f', function(err, author) { if (err) throw err; author.linkedin = 'https://www.linkedin.com/in/jamie-munro-8064ba1a/'; author.save(function(err) { if (err) throw err; console.log('Author updated successfully'); }); }); Author.findByIdAndUpdate('59b31406beefa1082819e72f', { linkedin: 'https://www.linkedin.com/in/jamie-munro-8064ba1a/' }, function(err, author) { if (err) throw err; console.log(author); }); });
Una vez más, puede ejecutar la aplicación con el comando: node index.js
.
Resumen
Después de leer este artículo, debe poder crear Esquemas y Modelos Mongoose extremadamente flexibles, aplicar validación simple o compleja, crear y actualizar documentos, y finalmente buscar los documentos que se crearon.
Espero que ahora te sientas cómodo usando Mongoose. Si desea obtener más información, le sugiero que revise las Guías de Mangoose, que profundizan en temas más avanzados, como población, middleware, promesas, etc.
¡Feliz cacería (pobre referencia de animales)!