Skip to main content

Understanding Mongoose Pre Hooks

· 4 min read
Sivabharathy

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

  1. Use next(): Always call next() at the end of your pre hook. If you encounter an error, pass it to next(err).

  2. 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 own this.

  3. Keep Logic Simple: Pre hooks should contain logic that is simple and does not perform heavy operations that could delay processing.

  4. 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.