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.

javascript
userSchema.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.

javascript
userSchema.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 document
  • save - Save document
  • remove - Remove document
  • init - Initialize document (load from database)

Query Operation Hooks

  • count - Count query
  • find - Find documents
  • findOne - Find single document
  • findOneAndDelete - Find and delete
  • findOneAndUpdate - Find and update
  • updateOne - Update single document
  • updateMany - Update multiple documents
  • deleteOne - Delete single document
  • deleteMany - 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)
javascript
userSchema.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)
javascript
userSchema.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

  1. Password Hashing: Encrypt password before saving user
  2. Timestamps: Automatically set createdAt and updatedAt
  3. Soft Delete: Mark as deleted before actual deletion
  4. Data Validation: Execute complex validation logic
  5. Logging: Record operation history
  6. Cache Invalidation: Update related caches
  7. Related Data: Automatically update related documents
  8. Notifications: Send notifications after operations

Important Notes

  1. Middleware executes in definition order
  2. Errors in pre hooks abort the operation
  3. Query middleware doesn't trigger document middleware
  4. Use { runValidators: true } with findOneAndUpdate to trigger validation
  5. Avoid infinite loops in middleware
标签:Mongoose