Middleware de Mongoose: hooks pre y post
Escribirá hooks de middleware para documentos y consultas destinados a tareas como aplicar hash a contraseñas antes de guardar o registrar información después de find.
Middleware de Mongoose: hooks pre y post es una lección gratuita de MongoDB Academy en CoddyKit. Esta es la lección 4 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 el middleware de Mongoose?
El middleware de Mongoose (también llamado hooks) está formado por funciones que se ejecutan antes o después de operaciones específicas como save, find, updateOne, deleteOne y otras. Permite insertar lógica personalizada en el ciclo de vida de las operaciones de documentos y consultas sin sobrecargar los manejadores de rutas. Entre sus usos habituales se incluyen aplicar hash a contraseñas antes de guardarlas, registrar los tiempos de las consultas, aplicar eliminaciones lógicas y completar datos relacionados después de find.
Dos tipos: middleware de documentos y de consultas
Mongoose tiene dos categorías distintas de middleware: el middleware de documentos se enlaza a operaciones sobre una instancia específica de Document (save, validate, remove, init). El middleware de consultas se enlaza a operaciones de consulta invocadas sobre el Model (find, findOne, updateOne, deleteOne, etc.). La diferencia clave es a qué hace referencia this: en el middleware de documentos, this es el documento; en el middleware de consultas, this es el objeto Query.
// Document middleware: 'this' = the document
userSchema.pre('save', function () {
console.log('Saving document:', this.email);
});
// Query middleware: 'this' = the Query object
userSchema.pre('find', function () {
console.log('Running query:', this.getQuery());
});Pre-save: aplicar hash a las contraseñas
El hook pre-save es el middleware de documentos más habitual. Se ejecuta antes de guardar un documento en MongoDB. El caso de uso canónico es aplicar hash a las contraseñas: cuando se guarda un documento de usuario con una contraseña nueva o modificada, aplíquele bcrypt antes de almacenarla. La comprobación this.isModified('password') evita volver a aplicar hash a una contraseña que ya tiene hash durante guardados no relacionados.
const bcrypt = require('bcrypt');
userSchema.pre('save', async function () {
// 'this' is the User document being saved
if (!this.isModified('password')) {
return; // skip if password hasn't changed
}
const saltRounds = 12;
this.password = await bcrypt.hash(this.password, saltRounds);
// The hashed value replaces the plain password before MongoDB stores it
});Ayudantes isNew e isModified
El middleware de documentos tiene acceso a ayudantes de seguimiento: this.isNew es true cuando el documento se inserta por primera vez (no cuando se actualiza). this.isModified(path) devuelve true si el campo especificado ha cambiado desde la última vez que se guardó o se obtuvo el documento. Estos ayudantes permiten ejecutar hooks de forma condicional: solo durante la creación o solo cuando cambia un campo específico.
userSchema.pre('save', async function () {
if (this.isNew) {
// Only runs when creating a new user, not on updates
this.verificationToken = crypto.randomBytes(32).toString('hex');
this.verificationExpires = new Date(Date.now() + 24 * 60 * 60 * 1000);
}
if (this.isModified('email')) {
// Only runs when the email field specifically changed
this.emailVerified = false; // reset verification on email change
}
});Post-save: efectos secundarios después de guardar
Los hooks post-save se ejecutan después de que un documento se haya guardado correctamente. Reciben el documento guardado como primer argumento y, en versiones antiguas de Mongoose, un callback next. Los hooks post son ideales para efectos secundarios que deben producirse después de un guardado correcto: enviar un correo electrónico de bienvenida, actualizar un índice de búsqueda, publicar un evento en una cola de mensajes o borrar una caché. Los errores en los hooks post no revierten el guardado.
userSchema.post('save', async function (doc) {
// 'doc' is the saved document
if (doc.isNew) {
// Note: 'isNew' is false here (doc was just saved, so it's no longer new)
// Track this with a flag set in pre-save:
}
});
// Pattern: set a flag in pre-save, read it in post-save
userSchema.pre('save', function () {
this._wasNew = this.isNew; // save state before it changes
});
userSchema.post('save', async function (doc) {
if (doc._wasNew) {
await sendWelcomeEmail(doc.email, doc.name);
await analyticsTracker.track('user_created', { userId: doc._id });
}
});Middleware de consultas: pre-find para eliminaciones lógicas
Un patrón clásico de middleware de consultas es implementar eliminaciones lógicas. En lugar de eliminar los documentos, establezca un campo deletedAt. Después, añada un hook pre('find') que agregue automáticamente { deletedAt: null } a cada consulta find, para garantizar que los documentos eliminados nunca se devuelvan de forma predeterminada. Esto proporciona un registro de auditoría y, al mismo tiempo, hace que la lógica de eliminación lógica sea transparente para el resto de la aplicación.
const postSchema = new mongoose.Schema({
title: String,
content: String,
deletedAt: { type: Date, default: null }
});
// Automatically exclude soft-deleted documents from all find queries
postSchema.pre(/^find/, function () {
// 'this' is the Query object
this.where({ deletedAt: null });
// /^find/ matches find, findOne, findOneAndUpdate, etc.
});
// Now Post.find({}) never returns deleted posts
// To explicitly query deleted posts, you'd call Post.find({}).bypassMiddleware() or use .lean() with the native driverMiddleware de consultas: pre-find para completar referencias automáticamente
Puede usar middleware de consultas para completar automáticamente un campo de referencia en cada find. Esto garantiza que un documento referenciado siempre se resuelva sin exigir a quienes llaman que añadan .populate() a cada consulta. Aunque resulta práctico, tenga cuidado: la cumplimentación automática añade una segunda consulta por cada find y puede degradar el rendimiento si la referencia es grande o no siempre se necesita.
const reviewSchema = new mongoose.Schema({
productId: { type: mongoose.Schema.Types.ObjectId, ref: 'Product' },
userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
rating: Number,
comment: String
});
// Always populate author info on find
reviewSchema.pre(/^find/, function () {
this.populate({
path: 'userId',
select: 'name avatar'
});
});
// Now Review.find() always includes user name and avatarPre-deleteOne: eliminaciones en cascada
Mongoose no aplica automáticamente las eliminaciones en cascada (eliminar documentos relacionados cuando se elimina un documento principal). Puede implementar el comportamiento en cascada mediante middleware de documentos. Un hook pre-deleteOne en un modelo User puede eliminar todas las publicaciones, comentarios y sesiones pertenecientes a ese usuario antes de eliminar el propio documento de usuario. Esto mantiene la integridad referencial sin restricciones de claves foráneas.
userSchema.pre('deleteOne', { document: true, query: false }, async function () {
// 'this' is the User document being deleted
const userId = this._id;
// Cascade delete related documents
await Promise.all([
Post.deleteMany({ authorId: userId }),
Comment.deleteMany({ userId: userId }),
Session.deleteMany({ userId: userId }),
Notification.deleteMany({ userId: userId })
]);
console.log('Cascade deleted data for user:', userId);
});
// Trigger:
// const user = await User.findById(id);
// await user.deleteOne(); // triggers pre-deleteOne aboveGestión de errores del middleware
Si una función pre-hook lanza un error o rechaza una Promise, se aborta la operación a la que precede. Esto permite realizar comprobaciones de validación o autorización en el middleware y abortar guardados o consultas lanzando un error. Por ejemplo, un hook pre-save que valide la lógica de negocio (no solo la validación del esquema) puede lanzar un error que llegue hasta el bloque catch de la llamada a .save() en el código de la aplicación.
orderSchema.pre('save', async function () {
if (this.total <= 0) {
throw new Error('Order total must be positive');
}
// Check inventory synchronously before saving the order
const product = await Product.findById(this.productId).lean();
if (!product || product.stock < this.quantity) {
throw new Error('Insufficient inventory for this order');
}
});
// In route handler:
try {
const order = new Order({ productId, quantity, total });
await order.save(); // throws if pre-save hook rejects
} catch (err) {
res.status(400).json({ error: err.message });
}Middleware de agregación
Mongoose también admite middleware para pipelines de agregación. Un hook pre-aggregate proporciona acceso al array del pipeline antes de enviarlo a MongoDB, lo que permite anteponer etapas (como filtrar documentos eliminados lógicamente) o añadir etapas al final (como insertar un límite predeterminado). Acceda al pipeline mediante this.pipeline() dentro de la función del hook.
postSchema.pre('aggregate', function () {
// 'this' is the Aggregate object
// Add a $match stage at the beginning to exclude soft-deleted documents
this.pipeline().unshift({
$match: { deletedAt: null }
});
});
// Now Post.aggregate([...]) automatically excludes deleted posts
// at the start of every aggregation pipelineProblemas del middleware: métodos de consulta que omiten los hooks
No todas las operaciones de escritura activan el middleware de documentos. updateMany(), findOneAndUpdate() y replaceOne() llamados sobre el Model (no sobre una instancia) omiten los hooks de save de documentos; son middleware de consultas y necesitan hooks independientes si desea interceptarlos. Por ejemplo, un hook de hash de contraseñas previo a save NO se ejecuta al llamar a User.updateOne({}, { $set: { password: plain } }). Realice siempre el hash en el código de la aplicación para las actualizaciones basadas en consultas.
// WRONG: password NOT hashed — bypasses pre-save hook
await User.updateOne({ _id: userId }, { $set: { password: plainPassword } });
// RIGHT for query-level updates: hash before calling updateOne
const hashed = await bcrypt.hash(plainPassword, 12);
await User.updateOne({ _id: userId }, { $set: { password: hashed } });
// Or: fetch, modify, save — triggers pre-save hook
const user = await User.findById(userId);
user.password = plainPassword; // hook will hash it
await user.save();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 aprendió que: los hooks previos se ejecutan antes de una operación y pueden cancelarla al lanzar un error; los hooks posteriores se ejecutan después y reciben el resultado como argumento, el middleware de documentos (pre-save, pre-deleteOne) usa 'this' como el documento, mientras que el middleware de consultas usa 'this' como el objeto Query, y los métodos de escritura a nivel de consulta (updateOne, updateMany, findOneAndUpdate) omiten el middleware de documentos; tenga siempre en cuenta qué hooks se activan para cada tipo de operación. ¡Con esto termina el itinerario del curso de MongoDB y bases de datos NoSQL!
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 «Middleware de Mongoose: hooks pre y post» es gratis?
Sí — el texto completo de «Middleware de Mongoose: hooks pre y post» 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 «Middleware de Mongoose: hooks pre y post»?
Escribirá hooks de middleware para documentos y consultas destinados a tareas como aplicar hash a contraseñas antes de guardar o registrar información después de find. 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 4 de 4.
¿Cuánto tiempo toma la lección «Middleware de Mongoose: hooks pre y post»?
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
- Conexión con el controlador oficial de Node.js
- Esquemas, modelos y virtuals de Mongoose
- Consultas, encadenamiento y documentos lean de Mongoose
- Middleware de Mongoose: hooks pre y post