5月29日 01:08
How do Mongoose middleware and hooks work, and what are their use cases?
Mongoose Middleware and Hooks are powerful features that allow executing custom logic before or after certain operations. Middleware is divided into two categories: Document Middleware and Query Middleware.
Middleware Types
1. Document Middleware
Operations performed on document instances, such as save(), validate(), remove(), etc.
javascriptuserSchema.pre('save', function(next) { console.log('About to save user:', this.name); next(); }); userSchema.post('save', function(doc) { console.log('User saved:', doc.name); });
2. Query Middleware
Operations performed on Model queries, such as find(), findOne(), updateOne(), etc.
javascriptuserSchema.pre('find', function() { this.where({ deleted: false }); }); userSchema.post('find', function(docs) { console.log('Found', docs.length, 'users'); });
Common Hooks
Document Operation Hooks
validate- Validate documentsave- Save documentremove- Remove documentinit- Initialize document (load from database)
Query Operation Hooks
count- Count queryfind- Find documentsfindOne- Find single documentfindOneAndDelete- Find and deletefindOneAndUpdate- Find and updateupdateOne- Update single documentupdateMany- Update multiple documentsdeleteOne- Delete single documentdeleteMany- Delete multiple documents
Difference Between Pre and Post Hooks
Pre Hooks
- Run before operation execution
- Can modify data or abort operation
- Must call
next()or return Promise - Can access
this(document instance or query object)
javascriptuserSchema.pre('save', function(next) { if (this.age < 0) { const err = new Error('Age cannot be negative'); return next(err); } this.email = this.email.toLowerCase(); next(); });
Post Hooks
- Run after operation execution
- Cannot modify data or abort operation
- Receive operation result as parameter
- Can access
this(document instance or query object)
javascriptuserSchema.post('save', function(doc) { console.log('User saved with ID:', doc._id); // Send notifications, log, etc. });
Async Middleware
Mongoose middleware supports async operations:
javascript// Using async/await userSchema.pre('save', async function(next) { const existing = await this.constructor.findOne({ email: this.email }); if (existing && existing._id.toString() !== this._id.toString()) { const err = new Error('Email already exists'); return next(err); } next(); }); // Return Promise userSchema.pre('save', function() { return checkEmailAvailability(this.email).then(isAvailable => { if (!isAvailable) { throw new Error('Email already exists'); } }); });
Practical Use Cases
- Password Hashing: Encrypt password before saving user
- Timestamps: Automatically set createdAt and updatedAt
- Soft Delete: Mark as deleted before actual deletion
- Data Validation: Execute complex validation logic
- Logging: Record operation history
- Cache Invalidation: Update related caches
- Related Data: Automatically update related documents
- Notifications: Send notifications after operations
Important Notes
- Middleware executes in definition order
- Errors in
prehooks abort the operation - Query middleware doesn't trigger document middleware
- Use
{ runValidators: true }withfindOneAndUpdateto trigger validation - Avoid infinite loops in middleware