Mongoose proporciona una solucion basada en esquemas para modelar datos de aplicaciones en MongoDB. Maneja conversion de tipos, validacion, construccion de consultas y hooks de logica de negocio, haciendolo el ODM mas popular en el ecosistema Node.js.
Cuando Usar Esto
- Necesitas una forma estructurada de interactuar con MongoDB desde Node.js. Consulta Parse JSON para manejo de documentos.
- Quieres validacion automatica y hooks de middleware
- Estas construyendo una API que requiere patrones relacionales en una base de datos documental. Consulta SQL Joins para patrones relacionales.
Requisitos Previos
- MongoDB instalado localmente o un cluster de MongoDB Atlas
- Node.js 18+
Solucion: Express + Mongoose
1. Instalar Dependencias
npm install express mongoose dotenv
2. Definir el Esquema
// models/User.js
import mongoose from 'mongoose';
const userSchema = new mongoose.Schema({
email: {
type: String,
required: [true, 'El email es obligatorio'],
unique: true,
lowercase: true,
trim: true,
},
name: {
type: String,
required: true,
minlength: 2,
maxlength: 100,
},
role: {
type: String,
enum: ['user', 'admin', 'moderator'],
default: 'user',
},
isActive: {
type: Boolean,
default: true,
},
}, {
timestamps: true,
});
// Indice para consultas comunes
userSchema.index({ email: 1 });
userSchema.index({ role: 1, isActive: 1 });
export default mongoose.model('User', userSchema);
3. Conectar y Realizar CRUD
// app.js
import express from 'express';
import mongoose from 'mongoose';
import User from './models/User.js';
const app = express();
app.use(express.json());
await mongoose.connect(process.env.MONGODB_URI);
// CREAR
app.post('/users', async (req, res) => {
try {
const user = await User.create(req.body);
res.status(201).json(user);
} catch (err) {
res.status(400).json({ error: err.message });
}
});
// LEER (con paginacion). Consulta [Pagination](/recipes/pagination/) para enfoques basados en cursor.
app.get('/users', async (req, res) => {
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
const skip = (page - 1) * limit;
const [users, total] = await Promise.all([
User.find({ isActive: true })
.select('-__v')
.sort({ createdAt: -1 })
.skip(skip)
.limit(limit)
.lean(),
User.countDocuments({ isActive: true }),
]);
res.json({
data: users,
pagination: { page, limit, total, pages: Math.ceil(total / limit) },
});
});
// LEER UNO
app.get('/users/:id', async (req, res) => {
const user = await User.findById(req.params.id).select('-__v');
if (!user) return res.status(404).json({ error: 'Usuario no encontrado' });
res.json(user);
});
// ACTUALIZAR
app.patch('/users/:id', async (req, res) => {
const user = await User.findByIdAndUpdate(
req.params.id,
req.body,
{ new: true, runValidators: true }
);
if (!user) return res.status(404).json({ error: 'Usuario no encontrado' });
res.json(user);
});
// ELIMINAR (eliminacion suave). Consulta [Soft Deletes](/recipes/soft-deletes/) para patrones.
app.delete('/users/:id', async (req, res) => {
const user = await User.findByIdAndUpdate(
req.params.id,
{ isActive: false },
{ new: true }
);
if (!user) return res.status(404).json({ error: 'Usuario no encontrado' });
res.json({ message: 'Usuario desactivado' });
});
app.listen(3000, () => console.log('Servidor ejecutandose en el puerto 3000'));
4. Transacciones
// Operaciones atomicas entre colecciones. Consulta [Database Transactions](/recipes/database-transactions/) para patrones ACID.
app.post('/orders', async (req, res) => {
const session = await mongoose.startSession();
session.startTransaction();
try {
const order = await Order.create([{ ...req.body, status: 'pending' }], { session });
await Product.updateOne(
{ _id: req.body.productId },
{ $inc: { stock: -req.body.quantity } },
{ session }
);
await session.commitTransaction();
res.status(201).json(order[0]);
} catch (err) {
await session.abortTransaction();
res.status(400).json({ error: err.message });
} finally {
session.endSession();
}
});
Como Funciona
- Definicion de Esquema impone estructura mientras preserva la flexibilidad de MongoDB
- Middleware Hooks ejecutan validacion y transformacion antes/despues de operaciones
- Construccion de Consultas proporciona una API encadenable para consultas complejas
- Transacciones aseguran cumplimiento ACID entre multiples documentos
Consideraciones de Produccion
- Habilita read preference
secondarypara cargas de trabajo intensivas en lectura en replica sets - Usa indices compuestos para campos de consulta frecuentemente combinados
- Implementa paginacion basada en cursor para grandes datasets en lugar de skip/limit. Consulta Pagination para patrones de cursor.
- Agrega plugins de Mongoose para patrones comunes (eliminacion suave, auditoria)
Tips de Rendimiento
- Usa
createIndexcon opción background. Previene que la creación de índices bloquee writes:
await User.collection.createIndex({ email: 1 }, { unique: true, background: true });
- Usa
bulkWritepara inserts batch. Reduce round-trips:
const docs = Array.from({ length: 1000 }, (_, i) => ({
insertOne: { document: { name: `User ${i}`, email: `user${i}@example.com` } },
}));
await User.bulkWrite(docs);
- Usa
hint()para forzar el uso de un índice. Cuando el query planner elige el índice incorrecto:
await User.find({ status: 'active' }).hint({ status: 1, createdAt: -1 });
- Usa
readPreferencepara replica sets. Enruta reads a secondaries:
mongoose.connect(uri, {
readPreference: 'secondaryPreferred',
});
- Monitorea queries lentos con profiling. Habilita el profiler de base de datos:
// Habilitar profiling para queries más lentos que 100ms
await mongoose.connection.db.command({
profile: 1,
slowms: 100,
});
// Consultar el profiler
const slowQueries = await mongoose.connection.db
.collection('system.profile')
.find({ millis: { $gt: 100 } })
.sort({ ts: -1 })
.limit(10)
.toArray(); Preguntas frecuentes
Debo usar Mongoose o el driver nativo?
Usa Mongoose para datos de aplicacion con necesidades de validacion. Usa el driver nativo para analiticas, pipelines de agregacion o maximo rendimiento.
Como manejo migraciones de esquema?
Usa migrate-mongo o escribe scripts de migracion idempotentes que se ejecuten en el despliegue.
Cuando debo usar referencias vs documentos embebidos?
Embebe cuando los datos se leen juntos y el crecimiento no acotado no se espera. Referencia cuando los datos se actualizan independientemente o crecen sin limite.
Recursos Relacionados
Guía de Diseño de Bases de Datos
Guía práctica para diseñar bases de datos relacionales con normalización, indexación y modelado de relaciones.
RecipeOptimizar consultas con índices de base de datos
Cómo crear, analizar y mantener índices para acelerar las consultas y evitar los errores de indexación más comunes.
PatternPatrón Repository
Abstrae la lógica de acceso a datos detrás de una interfaz limpia. Patrón de diseño arquitectural para capas de datos testeables y mantenibles.
RecipeTransacciones de Base de Datos en Python, JS y Java
Cómo usar transacciones ACID para garantizar integridad de datos en Python, JavaScript y Java con ejemplos SQL.
RecipeCómo usar agregaciones de Elasticsearch (con ejemplos)
Usá agregaciones de Elasticsearch para búsqueda facetada, métricas y análisis de series temporales. Ejemplos de terms, date_histogram, range y composite.