阿里云主机折上折
  • 微信号
Current Site:Index > The application of middleware

The application of middleware

Author:Chuan Chen 阅读数:8411人阅读 分类: MongoDB

The Application of Middleware

Middleware is a powerful feature in Mongoose that allows developers to insert custom logic at different stages of database operations. By intercepting and modifying data flow, it provides flexible control mechanisms. Whether for validation, transformation, or logging, middleware can play a role at key points in the data lifecycle.

Types of Middleware

Mongoose middleware is divided into four main types, corresponding to different operation stages:

  1. Document Middleware: Acts on document instances, including methods like init, validate, save, and remove.
  2. Model Middleware: Acts at the model level, such as static methods like insertMany and updateOne.
  3. Aggregate Middleware: Triggers before or after the execution of an aggregation pipeline.
  4. Query Middleware: Intercepts query operations like find, findOne, and countDocuments.

Usage of Document Middleware

Document middleware is commonly used to execute specific logic before or after saving a document. For example, you can automatically encrypt a password before saving user data:

userSchema.pre('save', async function(next) {
  if (!this.isModified('password')) return next();
  this.password = await bcrypt.hash(this.password, 10);
  next();
});

Another typical scenario is cleaning up related data after deleting a document:

postSchema.post('remove', async function(doc) {
  await Comment.deleteMany({ postId: doc._id });
});

Application of Query Middleware

Query middleware can modify query conditions or results. For instance, implementing soft delete functionality:

userSchema.pre(/^find/, function(next) {
  this.where({ isDeleted: false });
  next();
});

Or logging after a query completes:

productSchema.post('find', function(docs) {
  console.log(`Found ${docs.length} product records`);
});

Special Uses of Aggregate Middleware

Aggregate middleware is suitable for adding logic before or after complex data processing. For example, validating parameters before aggregation:

orderSchema.pre('aggregate', function() {
  this.pipeline().unshift({ $match: { status: 'completed' } });
});

Error Handling Middleware

Mongoose allows defining dedicated error-handling middleware:

userSchema.post('save', function(error, doc, next) {
  if (error.name === 'MongoError' && error.code === 11000) {
    next(new Error('Username already exists'));
  } else {
    next(error);
  }
});

Execution Order of Middleware

Multiple middleware functions execute in the order they are declared. For middleware of the same type:

schema.pre('save', function() { console.log('First middleware'); });
schema.pre('save', function() { console.log('Second middleware'); });

The logs will be output in order. Pre-processing middleware (pre) executes before post-processing middleware (post).

Performance Considerations for Middleware

While middleware is powerful, overuse can impact performance, especially in bulk operations:

// Inefficient implementation  
userSchema.pre('updateMany', async function() {
  // Complex logic  
});  

// Better approach  
userSchema.pre('updateMany', function() {
  if (this.getOptions().skipMiddleware) return;
  // Conditionally executed logic  
});

Practical Use Cases

An e-commerce system's inventory management can leverage middleware:

productSchema.pre('save', function() {
  if (this.stock < 0) throw new Error('Stock cannot be negative');
});  

orderSchema.post('save', async function() {
  await Product.updateOne(
    { _id: this.productId },
    { $inc: { stock: -this.quantity } }
  );
});

Debugging Techniques for Middleware

When debugging middleware, you can add trace information:

schema.pre('save', function(next) {
  console.log('Middleware triggered:', this.constructor.modelName);
  console.log('Document data:', this);
  next();
});

Or use Mongoose's debugging feature:

mongoose.set('debug', function(collectionName, method, ...args) {
  console.log(`Mongoose: ${collectionName}.${method}`, args);
});

Limitations and Considerations

Certain operations do not trigger middleware:

// Does not trigger save middleware  
User.updateOne({ _id }, { $set: { name: 'New Name' } });  

// Does trigger  
const user = await User.findById(id);
user.name = 'New Name';
await user.save();

Bulk operations may require special handling:

User.insertMany([...], { middleware: true });  // Explicitly enable middleware  

Advanced Middleware Patterns

For complex business logic, you can combine multiple middleware functions:

const auditLog = (schema) => {
  schema.pre(['save', 'remove'], function() {
    this._wasNew = this.isNew;
  });  

  schema.post(['save', 'remove'], function(doc) {
    AuditLog.create({
      action: this.op,
      model: doc.constructor.modelName,
      documentId: doc._id,
      wasNew: this._wasNew
    });
  });
};  

userSchema.plugin(auditLog);

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.