Intergiciels Mongoose : hooks avant et après
Les apprenants écriront des hooks d’intergiciel pour les documents et les requêtes, afin d’effectuer des tâches comme le hachage des mots de passe avant l’enregistrement ou la journalisation après une recherche.
Intergiciels Mongoose : hooks avant et après est une leçon MongoDB Academy gratuite sur CoddyKit. Ceci est la leçon 4 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage MongoDB Academy, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours MongoDB Academy comprend 4 leçons au total.
Qu’est-ce que l’intergiciel Mongoose ?
L’intergiciel Mongoose (également appelé hooks) se compose de fonctions exécutées avant ou après certaines opérations telles que save, find, updateOne, deleteOne, et bien d’autres. Il vous permet d’injecter une logique personnalisée dans le cycle de vie des opérations sur les documents et les requêtes sans encombrer les gestionnaires de routes. Parmi les utilisations courantes figurent le hachage des mots de passe avant l’enregistrement, la journalisation de la durée des requêtes, l’application de suppressions logiques et le peuplement des données associées après find.
Deux types : intergiciel de document et de requête
Mongoose possède deux catégories distinctes d’intergiciel : l’intergiciel de document se rattache aux opérations effectuées sur une instance Document donnée (save, validate, remove, init). L’intergiciel de requête se rattache aux opérations de requête invoquées sur le Model (find, findOne, updateOne, deleteOne, etc.). La différence essentielle concerne la référence de this : dans l’intergiciel de document, this désigne le document ; dans l’intergiciel de requête, this désigne l’objet de requête.
// 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 : hacher les mots de passe
Le hook pre-save est l’intergiciel de document le plus courant. Il s’exécute avant l’enregistrement d’un document dans MongoDB. Son cas d’utilisation classique est le hachage des mots de passe : lorsqu’un document utilisateur est enregistré avec un mot de passe nouveau ou modifié, il faut le hacher avec bcrypt avant de le stocker. La vérification this.isModified('password') empêche de hacher à nouveau un mot de passe déjà haché lors d’enregistrements sans rapport.
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
});Assistants isNew et isModified
L’intergiciel de document a accès à des assistants de suivi : this.isNew vaut true lorsque le document est inséré pour la première fois (et non mis à jour). this.isModified(path) renvoie true si le champ indiqué a été modifié depuis le dernier enregistrement ou la dernière récupération du document. Ces assistants vous permettent d’exécuter des hooks sous certaines conditions — uniquement lors de la création ou uniquement lorsqu’un champ précis change.
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 : effets secondaires après l’enregistrement
Les hooks post-save s’exécutent après la persistance réussie d’un document. Ils reçoivent le document enregistré comme premier argument et, dans les anciennes versions de Mongoose, un rappel next. Les hooks post sont idéaux pour les effets secondaires qui doivent se produire après un enregistrement réussi : envoyer un e-mail de bienvenue, mettre à jour un index de recherche, publier un événement dans une file de messages ou vider un cache. Les erreurs des hooks post n’annulent pas l’enregistrement.
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 });
}
});Intergiciel de requête : pre-find pour les suppressions logiques
Un modèle classique d’intergiciel de requête consiste à implémenter les suppressions logiques. Au lieu de supprimer les documents, définissez un champ deletedAt. Ajoutez ensuite un hook pre('find') qui ajoute automatiquement { deletedAt: null } à chaque requête find, afin que les documents supprimés ne soient jamais renvoyés par défaut. Cette approche fournit une piste d’audit tout en rendant la logique de suppression logique transparente pour le reste de l’application.
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 driverIntergiciel de requête : peuplement automatique avec pre-find
Vous pouvez utiliser l’intergiciel de requête pour peupler automatiquement un champ de référence lors de chaque find. Ainsi, un document référencé est toujours résolu sans obliger les appelants à ajouter .populate() à chaque requête. Cette approche est pratique, mais soyez prudent : le peuplement automatique ajoute une deuxième requête à chaque find et peut dégrader les performances si la référence est volumineuse ou n’est pas toujours nécessaire.
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 : suppressions en cascade
Mongoose n’applique pas automatiquement les suppressions en cascade (la suppression des documents associés lorsqu’un parent est supprimé). Vous pouvez implémenter un comportement en cascade avec l’intergiciel de document. Un hook pre-deleteOne sur un modèle User peut supprimer tous les articles, commentaires et sessions appartenant à cet utilisateur avant de supprimer le document utilisateur lui-même. Cela préserve l’intégrité référentielle sans contraintes de clé étrangère.
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 aboveGestion des erreurs de l’intergiciel
Si une fonction de hook pre lève une erreur ou rejette une promesse, l’opération qu’elle précède est interrompue. Vous pouvez ainsi effectuer des vérifications de validation ou d’autorisation dans l’intergiciel et interrompre des enregistrements ou des requêtes en levant une erreur. Par exemple, un hook pre-save qui valide la logique métier (et pas seulement la validation du schéma) peut lever une erreur qui remonte jusqu’au bloc catch de l’appel .save() dans le code de l’application.
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 });
}Intergiciel d’agrégation
Mongoose prend également en charge l’intergiciel pour les chaînes d’agrégation. Un hook pre-aggregate vous donne accès au tableau de la chaîne avant son envoi à MongoDB, ce qui permet d’ajouter des étapes au début (par exemple pour filtrer les documents supprimés logiquement) ou à la fin (par exemple pour injecter une limite par défaut). Accédez à la chaîne via this.pipeline() dans la fonction du 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 pipelinePièges de l’intergiciel : méthodes de requête qui contournent les hooks
Toutes les opérations d’écriture ne déclenchent pas le middleware des documents. updateMany(), findOneAndUpdate() et replaceOne() appelées sur le modèle (et non sur une instance) contournent les fonctions d’interception save des documents : ce sont des middlewares de requête, qui nécessitent des fonctions d’interception distinctes si vous souhaitez les intercepter. Par exemple, une fonction d’interception de hachage du mot de passe avant l’enregistrement ne s’exécute PAS lorsque vous appelez User.updateOne({}, { $set: { password: plain } }). Hachez toujours le mot de passe dans le code de l’application lors des mises à jour effectuées par requête.
// 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();Vérification rapide
Testez votre compréhension des concepts de MongoDB et des bases de données NoSQL présentés dans cette leçon.
Récapitulatif de la leçon
Dans cette leçon, vous avez appris que les fonctions d’interception préalables s’exécutent avant une opération et peuvent l’annuler en levant une erreur ; les fonctions d’interception postérieures s’exécutent après l’opération et reçoivent son résultat comme argument, que le middleware des documents (pré-enregistrement, pré-deleteOne) utilise « this » comme document, tandis que le middleware des requêtes utilise « this » comme objet de requête, et que les méthodes d’écriture au niveau des requêtes (updateOne, updateMany, findOneAndUpdate) contournent le middleware des documents : vérifiez toujours quelles fonctions d’interception s’exécutent pour chaque type d’opération. Vous avez ainsi terminé le parcours de cours MongoDB et bases de données NoSQL !
Apprends JavaScript avec un tuteur IA — gratuit
Écris et exécute du vrai code dans ton navigateur, obtiens de l'aide instantanée d'un tuteur IA disponible 24h/24, et reprends là où tu t'es arrêté sur le web ou dans l'app.
- Cours
- 30
- Leçons
- 120
Questions Fréquemment Posées
La leçon « Intergiciels Mongoose : hooks avant et après » est-elle gratuite ?
Oui — le texte complet de « Intergiciels Mongoose : hooks avant et après » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours MongoDB Academy, passe à CoddyKit PRO. Le cours MongoDB Academy comprend 4 leçons au total.
Qu'est-ce que j'apprendrai dans « Intergiciels Mongoose : hooks avant et après » ?
Les apprenants écriront des hooks d’intergiciel pour les documents et les requêtes, afin d’effectuer des tâches comme le hachage des mots de passe avant l’enregistrement ou la journalisation après un… Tu pratiques MongoDB Academy avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.
Dois-je avoir de l'expérience pour commencer MongoDB Academy ?
Aucune expérience préalable n'est requise. MongoDB Academy sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 4 sur 4.
Combien de temps prend la leçon « Intergiciels Mongoose : hooks avant et après » ?
La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.
Peux-tu écrire et exécuter du code dans cette leçon MongoDB Academy ?
Oui. Chaque leçon MongoDB Academy inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.
Toutes les leçons de ce cours
- Se connecter avec le pilote Node.js officiel
- Schémas, modèles et propriétés virtuelles Mongoose
- Requêtes Mongoose, chaînage et documents allégés
- Intergiciels Mongoose : hooks avant et après