MongoDB Academy · Lección

Esquemas, modelos y virtuals de Mongoose

Definirá esquemas de Mongoose con opciones de tipo, validación y valores predeterminados, creará modelos y añadirá propiedades virtuales.

Lección 2 de 413 pasos

Esquemas, modelos y virtuals de Mongoose es una lección gratuita de MongoDB Academy en CoddyKit. Esta es la lección 2 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de MongoDB Academy, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de MongoDB Academy incluye 4 lecciones en total.

¿Qué es Mongoose?

Mongoose es un Object Document Mapper (ODM) para MongoDB y Node.js. Se sitúa sobre el controlador oficial de MongoDB y añade una capa de abstracción: esquemas para validar la estructura de los documentos, modelos para interactuar con la base de datos, hooks de middleware para la lógica previa y posterior a las operaciones y virtuals para los campos calculados. Mongoose es la biblioteca de MongoDB más popular del ecosistema de Node.js y resulta especialmente productiva para aplicaciones de servidor con modelos de datos bien definidos.

// Install Mongoose
// npm install mongoose

const mongoose = require('mongoose');

// Connect to MongoDB
await mongoose.connect(process.env.MONGODB_URI);
console.log('Mongoose connected to MongoDB');

Definición de un esquema

Un Schema de Mongoose define la estructura, los tipos y las restricciones de los documentos de una colección. Cada clave del esquema corresponde a un campo del documento. Puede especificar por campo el tipo, required, default, min, max, enum y muchas otras opciones de validación. Los esquemas son la única fuente de verdad de la estructura de sus documentos en una aplicación de Mongoose.

const { Schema } = mongoose;

const userSchema = new Schema({
  name: {
    type: String,
    required: [true, 'Name is required'],
    trim: true,
    maxlength: 100
  },
  email: {
    type: String,
    required: true,
    unique: true,
    lowercase: true  // automatically converts to lowercase before saving
  },
  age: {
    type: Number,
    min: [0, 'Age cannot be negative'],
    max: 150
  },
  role: {
    type: String,
    enum: ['admin', 'user', 'guest'],
    default: 'user'
  },
  createdAt: {
    type: Date,
    default: Date.now
  }
});

Tipos de esquema: tipos compatibles

Mongoose admite un amplio conjunto de tipos de esquema. Los más comunes son String, Number, Date, Boolean, Buffer, mongoose.Schema.Types.ObjectId (para referencias), Array (indicado como [Type]) y Mixed (cualquier valor, sin comprobación de tipos). Mongoose también admite esquemas anidados (subdocumentos) al anidar la definición de un Schema dentro de la definición de campo de otro Schema.

const { Schema } = mongoose;
const { ObjectId } = Schema.Types;

const orderSchema = new Schema({
  customerId: { type: ObjectId, ref: 'User', required: true }, // reference to User model
  items: [
    {
      productId: { type: ObjectId, ref: 'Product' },
      quantity: { type: Number, min: 1 },
      price: Number
    }
  ],
  total: Number,
  status: { type: String, default: 'pending' },
  metadata: Schema.Types.Mixed,  // accepts any shape
  tags: [String],                // array of strings
  shippedAt: Date
});

Opción de marcas de tiempo

Pasar { timestamps: true } como segundo argumento al constructor de Schema hace que Mongoose administre automáticamente los campos createdAt y updatedAt de cada documento. createdAt se establece una vez al insertar el documento y updatedAt se actualiza en cada guardado. No necesita establecer estos campos manualmente: Mongoose los gestiona de forma transparente. Esta es una práctica recomendada para todos los esquemas de producción.

const productSchema = new Schema(
  {
    name: { type: String, required: true },
    price: { type: Number, required: true },
    category: String,
    stock: { type: Number, default: 0 }
  },
  { timestamps: true }  // adds createdAt and updatedAt automatically
);

// Documents will have:
// { name: 'Laptop', price: 999, createdAt: Date, updatedAt: Date }

Creación de un modelo

Un Model es un constructor compilado a partir de un Schema. Proporciona la interfaz para consultar y escribir documentos en la colección. Llame a mongoose.model('ModelName', schema) para crear un modelo; el primer argumento es el nombre singular de la colección (Mongoose lo convierte automáticamente en plural: 'User' → colección 'users'). Los modelos deben crearse una sola vez y exportarse como módulos.

// Define schema
const userSchema = new mongoose.Schema({
  name: String,
  email: { type: String, unique: true },
  role: { type: String, default: 'user' }
}, { timestamps: true });

// Compile the model
const User = mongoose.model('User', userSchema);
// This creates/uses the 'users' collection

module.exports = User;

// Usage in another file:
// const User = require('./models/user');
// const user = await User.findOne({ email: 'alice@example.com' });

Creación de documentos con new Model()

Cree una nueva instancia de documento mediante el constructor del modelo: new User({ name: '...', ... }). Esto crea un objeto de documento en memoria con validación, pero NO lo guarda en la base de datos. Llame a .save() en la instancia para persistirlo, o utilice la abreviatura estática User.create(), que combina ambos pasos. Mongoose valida el documento según el esquema antes de guardarlo y lanza un ValidationError si se infringen las restricciones.

// Method 1: new + save (two-step)
const user = new User({
  name: 'Alice',
  email: 'alice@example.com',
  role: 'admin'
});
await user.save(); // validates then saves to 'users' collection

// Method 2: User.create() shorthand
const user2 = await User.create({
  name: 'Bob',
  email: 'bob@example.com'
});
console.log('Created user ID:', user2._id);

// Method 3: insertMany for bulk
await User.insertMany([
  { name: 'Carol', email: 'carol@example.com' },
  { name: 'Dave', email: 'dave@example.com' }
]);

Propiedades virtuales

Los virtuals son propiedades calculadas que no se almacenan en la base de datos, sino que se calculan a partir de otros campos del documento. Se comportan como campos de documento normales en el código de su aplicación, pero nunca se escriben en MongoDB. Algunos usos habituales son combinar firstName y lastName en un virtual fullName, calcular age a partir de un campo birthDate o crear una url pública a partir de un _id.

const personSchema = new mongoose.Schema({
  firstName: String,
  lastName: String,
  birthDate: Date
});

// Virtual: combines firstName and lastName
personSchema.virtual('fullName').get(function () {
  return this.firstName + ' ' + this.lastName;
  // Use regular function (not arrow function) to access 'this'
});

// Virtual with a setter for convenience
personSchema.virtual('fullName').get(function () {
  return this.firstName + ' ' + this.lastName;
}).set(function (v) {
  this.firstName = v.split(' ')[0];
  this.lastName = v.split(' ')[1];
});

const Person = mongoose.model('Person', personSchema);
const p = new Person({ firstName: 'Alice', lastName: 'Smith' });
console.log(p.fullName); // 'Alice Smith'

Inclusión de virtuals en la salida JSON

De forma predeterminada, los virtuals no se incluyen al convertir un documento a JSON (por ejemplo, al enviarlo en la respuesta de una API). Para incluirlos, establezca { toJSON: { virtuals: true } } en las opciones del esquema o llame explícitamente a doc.toJSON({ virtuals: true }). En aplicaciones de Express, res.json(doc) llama automáticamente a toJSON(), por lo que establecer toJSON: { virtuals: true } en el esquema es la forma más clara de incluirlos siempre.

const userSchema = new mongoose.Schema(
  {
    firstName: String,
    lastName: String
  },
  {
    toJSON: { virtuals: true },    // include virtuals in res.json()
    toObject: { virtuals: true }   // include virtuals in .toObject()
  }
);

userSchema.virtual('fullName').get(function () {
  return this.firstName + ' ' + this.lastName;
});

const user = new User({ firstName: 'Alice', lastName: 'Smith' });
console.log(JSON.stringify(user)); // includes 'fullName': 'Alice Smith'

Validación personalizada en esquemas

Los esquemas de Mongoose admiten funciones de validación personalizadas para cada campo. La función de validación recibe el valor del campo y debe devolver true si es válido o false (o lanzar un error) si no lo es. También puede proporcionar un mensaje de error personalizado. Las validaciones personalizadas se ejecutan antes de .save() y pueden ser asíncronas, lo que resulta útil para comprobaciones de unicidad a nivel de base de datos que van más allá del índice unique.

const productSchema = new mongoose.Schema({
  name: String,
  price: {
    type: Number,
    required: true,
    validate: {
      validator: function (v) {
        return v > 0; // price must be positive
      },
      message: props => 'Price must be positive, got ' + props.value
    }
  },
  sku: {
    type: String,
    validate: {
      validator: function (v) {
        return /^[A-Z]{2}-\d{4}$/.test(v); // format: AB-1234
      },
      message: 'SKU must match format AB-1234'
    }
  }
});

Métodos y estáticos de esquemas

Los esquemas admiten métodos de instancia (disponibles en cada documento) y métodos estáticos (llamados en la clase Model). Los métodos de instancia acceden a this para referirse al documento específico, por lo que son ideales para operaciones propias de un documento, como comparar contraseñas o dar formato a la salida. Los métodos estáticos resultan útiles para consultas comunes o funciones de fábrica que no operan sobre una instancia específica.

const userSchema = new mongoose.Schema({ email: String, passwordHash: String });

// Instance method: available on each user document
userSchema.methods.checkPassword = function (candidatePassword) {
  return bcrypt.compare(candidatePassword, this.passwordHash);
};

// Static method: called on the User model
userSchema.statics.findByEmail = function (email) {
  return this.findOne({ email: email.toLowerCase() });
};

const User = mongoose.model('User', userSchema);

// Usage:
const user = await User.findByEmail('alice@example.com');  // static
const valid = await user.checkPassword('secret');          // instance method

Subdocumentos frente a objetos de esquema anidados

Mongoose distingue entre subdocumentos incrustados (definidos como un array de esquemas) y objetos de esquema anidados (una definición de esquema simple dentro de un campo). Cada subdocumento de un array recibe su propio _id y puede manipularse como un documento individual mediante doc.items.id(subId). Los objetos anidados comparten el ciclo de vida del documento principal. Use subdocumentos en arrays para colecciones ordenadas de registros (líneas de pedido, comentarios). Use objetos anidados para estructuras incrustadas uno a uno (dirección, metadatos).

// Nested object — no array, no _id per entry
const userSchema = new Schema({
  address: {
    street: String,
    city: String,
    zip: String
  }
});

// Array subdocuments — each item gets its own _id
const orderSchema = new Schema({
  items: [
    {
      productId: Schema.Types.ObjectId,
      quantity: Number,
      price: Number
      // _id auto-added to each item
    }
  ]
});

// Access subdocument by ID:
const item = order.items.id(someItemId);

Comprobación rápida

Compruebe su comprensión de los conceptos de MongoDB y las bases de datos NoSQL de esta lección.

Resumen de la lección

En esta lección ha aprendido que: los esquemas de Mongoose definen la estructura de los documentos, los tipos y las reglas de validación; los modelos se compilan a partir de esquemas y proporcionan la interfaz de consulta y escritura (Model.find(), new Model(), etc.); y los virtuals son propiedades calculadas que existen en memoria, pero nunca se almacenan en MongoDB; resultan útiles para campos derivados como fullName o url. A continuación exploraremos la API de consultas de Mongoose, incluido el encadenamiento, .lean() y la comparación con el driver nativo.

Gratis para empezar

Aprende JavaScript con un tutor de IA — gratis

Escribe y ejecuta código real en tu navegador, obtén ayuda instantánea de un tutor de IA disponible 24/7 y continúa donde lo dejaste en la web o en la aplicación.

Cursos
30
Lecciones
120

Preguntas frecuentes

¿La lección «Esquemas, modelos y virtuals de Mongoose» es gratis?

Sí — el texto completo de «Esquemas, modelos y virtuals de Mongoose» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de MongoDB Academy, actualiza a CoddyKit PRO. El curso de MongoDB Academy incluye 4 lecciones en total.

¿Qué aprenderé en «Esquemas, modelos y virtuals de Mongoose»?

Definirá esquemas de Mongoose con opciones de tipo, validación y valores predeterminados, creará modelos y añadirá propiedades virtuales. Practicas MongoDB Academy con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.

¿Necesito experiencia previa para empezar MongoDB Academy?

No se requiere experiencia previa. MongoDB Academy en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 2 de 4.

¿Cuánto tiempo toma la lección «Esquemas, modelos y virtuals de Mongoose»?

La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.

¿Puedo escribir y ejecutar código en esta lección de MongoDB Academy?

Sí. Cada lección de MongoDB Academy incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.

Todas las lecciones de este curso

  1. Conexión con el controlador oficial de Node.js
  2. Esquemas, modelos y virtuals de Mongoose
  3. Consultas, encadenamiento y documentos lean de Mongoose
  4. Middleware de Mongoose: hooks pre y post
← Volver a MongoDB Academy