Mongoose pre hooks are middleware functions that run before certain operations, such as saving or validating a document. They are useful for a variety of tasks, including data validation, transformation, and managing related data.
Why Use Pre Hooks?
Pre hooks allow you to:
- Validate or sanitize data before it’s saved to the database.
- Automatically perform actions like hashing passwords.
- Clean up related data before deletion.
- Modify queries before executing them.
Common Mongoose Pre Hooks
Here are some of the most commonly used pre hooks in Mongoose, along with examples.
1. pre('save')
This hook is triggered before a document is saved. It's often used to perform validation or modify data.
Example: Hashing a Password
const bcrypt = require('bcrypt');
const mongoose = require('mongoose');
const userSchema = new mongoose.Schema({
name: String,
email: { type: String, unique: true },
password: String,
});
// Hash password before saving the user
userSchema.pre('save', function(next) {
if (this.isModified('password')) {
bcrypt.hash(this.password, 10, (err, hash) => {
if (err) return next(err);
this.password = hash; // Set the hashed password
next();
});
} else {
next();
}
});
const User = mongoose.model('User', userSchema);
2. pre('validate')
This hook is called before the validation occurs. It can be used to validate or modify fields prior to validation.
Example: Ensure Email is Lowercase
userSchema.pre('validate', function(next) {
if (this.email) {
this.email = this.email.toLowerCase(); // Convert email to lowercase
}
next();
});
3. pre('remove')
Triggered before a document is removed, this hook can be useful for cleaning up related data.
Example: Cleaning Up Related Posts
const postSchema = new mongoose.Schema({
title: String,
author: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
});
userSchema.pre('remove', function(next) {
// Remove all posts by this user
Post.deleteMany({ author: this._id }, (err) => {
next(err);
});
});
4. pre('update')
This hook runs before a document is updated. It allows you to modify the update operation before it executes.
Example: Set Updated At Timestamp
userSchema.pre('update', function(next) {
this.set({ updatedAt: Date.now() }); // Set the updatedAt field
next();
});
5. pre('find')
and pre('findOne')
These hooks are triggered before find
and findOne
operations, allowing you to modify the query.
Example: Adding a Condition
userSchema.pre('find', function() {
this.where({ isActive: true }); // Only find active users
});
userSchema.pre('findOne', function() {
this.where({ isActive: true }); // Only find active users
});
6. pre('aggregate')
This hook runs before an aggregation operation, allowing you to modify the aggregation pipeline.
Example: Adding a Custom Match Stage
userSchema.pre('aggregate', function() {
this.pipeline().unshift({ $match: { isActive: true } }); // Only include active users in the aggregation
});
7. pre('insertMany')
Triggered before multiple documents are inserted, this hook allows for processing each document.
Example: Capitalizing User Names
userSchema.pre('insertMany', function(next, docs) {
docs.forEach(doc => {
if (doc.name) {
doc.name = doc.name.charAt(0).toUpperCase() + doc.name.slice(1);
}
});
next();
});
How to Use Pre Hooks
Pre hooks are defined within your schema. You can chain multiple pre hooks for a single event.
const mongoose = require('mongoose');
const userSchema = new mongoose.Schema({
name: String,
email: String,
password: String,
});
// Example of multiple pre hooks
userSchema.pre('save', function(next) {
// Code to run before saving
next();
});
userSchema.pre('validate', function(next) {
// Code to run before validation
next();
});
const User = mongoose.model('User', userSchema);
Best Practices
Use
next()
: Always callnext()
at the end of your pre hook. If you encounter an error, pass it tonext(err)
.Be Cautious with
this
: Inside pre hooks,this
refers to the document being processed. Make sure to handle it correctly, especially when using arrow functions, as they do not bind their ownthis
.Keep Logic Simple: Pre hooks should contain logic that is simple and does not perform heavy operations that could delay processing.
Error Handling: Always handle errors gracefully to avoid unexpected crashes.
Conclusion
Mongoose pre hooks are powerful tools that help you enforce data integrity, perform validations, and manage related data effectively. By understanding and utilizing these hooks, you can create more robust and maintainable applications. Whether you’re hashing passwords, cleaning up data, or ensuring consistent formats, pre hooks can streamline your Mongoose workflows and enhance your application’s functionality.