Skip to content

Conversation

@AbdelrahmanHafez
Copy link
Collaborator

@AbdelrahmanHafez AbdelrahmanHafez commented Dec 9, 2025

Summary

This PR adds a middleware option to Mongoose operations that allows skipping pre and/or post hooks selectively. This addresses #8768.

The Problem

Users needed a way to skip middleware for scenarios like:

  • Data migrations where hooks interfere with bulk processing
  • Performance-critical paths where hook overhead matters
  • Administrative operations that should bypass business logic
  • Avoiding infinite loops when hooks need to perform the same operation

Existing workarounds required manual effort:

  • Pass custom options and check them inside each hook (this.getOptions().disableMiddlewares)
  • Use Model.collection.findOne() which bypasses Mongoose entirely
  • Use Model.bulkWrite() for save operations (skips all middleware)

This PR provides a first-class option that works consistently across all operations.

The Solution

The new middleware option accepts three forms:

// Skip all hooks
await Model.find({}, null, { middleware: false });
await doc.save({ middleware: false });

// Skip only pre hooks
await Model.updateOne({}, { name: 'x' }, { middleware: { pre: false } });

// Skip only post hooks  
await Model.insertMany([...], { middleware: { post: false } });

Key Implementation Details

Supported operations: find, findOne, findOneAndUpdate, findOneAndDelete, findOneAndReplace, updateOne, updateMany, deleteOne, deleteMany, countDocuments, replaceOne, save, validate, insertMany, bulkWrite, aggregate, and document-level updateOne.

Subdocument cascading: When saving a parent document with middleware: false, the option cascades to all subdocuments - their hooks are also skipped.

Validation is NOT affected: The middleware option on save() only skips save hooks - validation still runs. This is intentional because validation is a safety feature. To skip validation, use the existing validateBeforeSave: false option.

To support this, I moved validation logic out of the validateBeforeSave plugin (which was a pre-save hook) into a new $__validateBeforeSave() method called directly in $__save(). This ensures validation runs regardless of the middleware option.
This is probably worth discussing whether or not it's a safe change to introduce, currently all tests pass, and even though if it was common knowledge that validateBeforeSave was a pre-hook, I can't think of any way it being moved to a normal method call would break users applications, it's an implementation detail that's hidden.

What do you think? @vkarpov15 @hasezoey

…pre-hook

Move validateBeforeSave from a pre-save hook plugin to $__validateBeforeSave()
called directly in $__save(). This ensures validation runs regardless of the
new `middleware` option (gh-8768).

Previously, `middleware: false` would skip the validateBeforeSave pre-hook,
causing documents to save without validation. Now validation always runs
unless explicitly disabled with `validateBeforeSave: false`.
@AbdelrahmanHafez AbdelrahmanHafez marked this pull request as ready for review December 9, 2025 14:42
@AbdelrahmanHafez AbdelrahmanHafez changed the base branch from master to gh-15881 December 9, 2025 14:43
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a new middleware option to Mongoose that allows selectively skipping pre and/or post hooks across all major operations. The feature addresses use cases like data migrations, performance-critical paths, and avoiding infinite loops in hooks. The implementation is comprehensive and includes proper TypeScript definitions, extensive test coverage, and consistent error handling patterns.

Key Changes

  • Added middleware option accepting false or { pre?: boolean, post?: boolean } to skip hooks
  • Moved validation logic from validateBeforeSave pre-hook plugin to direct $__validateBeforeSave() method call to ensure validation runs regardless of middleware option
  • Added shouldSkipMiddleware() helper function for consistent middleware skip checking

Reviewed changes

Copilot reviewed 10 out of 14 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
types/query.d.ts Added middleware option to MongooseBaseQueryOptionKeys and QueryOptions
types/models.d.ts Added middleware option to SaveOptions, InsertManyOptions, and MongooseBulkWriteOptions
types/middlewares.d.ts Defined SkipMiddlewareOptions interface for granular pre/post control
types/aggregate.d.ts Added middleware option to AggregateOptions
test/types/model.skip.middleware.test.ts TypeScript tests verifying type definitions
test/model.skip.middleware.test.js Comprehensive tests for all operations including subdocuments and error propagation
test/model.middleware.test.js Tests for pre-hook error propagation in bulkWrite
test/model.insertMany.test.js Tests for pre-hook error propagation in insertMany
lib/types/subdocument.js Updated subdocument save methods to accept and pass options parameter
lib/query.js Modified _executePreHooks and _executePostHooks to check middleware skip option
lib/plugins/validateBeforeSave.js Removed - validation logic moved to Model.prototype
lib/plugins/index.js Removed validateBeforeSave plugin export
lib/model.js Added $__validateBeforeSave() method; updated $__save(), insertMany(), and bulkWrite() to support middleware skipping
lib/helpers/query/shouldSkipMiddleware.js New helper function to determine if middleware should be skipped
lib/document.js Updated _execDocumentPreHooks and _execDocumentPostHooks to accept options and check skip conditions
lib/aggregate.js Updated exec() to support middleware skipping with proper error handling

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Collaborator

@hasezoey hasezoey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

* ignore
*/

Model.prototype.$__validateBeforeSave = async function $__validateBeforeSave(options) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is now a function on Model, maybe it should get some JSDoc.

@AbdelrahmanHafez
Copy link
Collaborator Author

AbdelrahmanHafez commented Dec 11, 2025

Note to self:
Find out if we need to do something with this code block in model.js around line 3650, and all other .execPre(' parts in the codebase.
The skip-middlewares feature should not change the behavior for anything internal and should only enable/disable user-defined middlewares.

function buildPreSavePromise(document, options) {
  return new Promise((resolve, reject) => {
    document.schema.s.hooks.execPre('save', document, [options], (err) => {
      if (err) {
        reject(err);
        return;
      }
      resolve();
    });
  });
}

@AbdelrahmanHafez AbdelrahmanHafez changed the base branch from gh-15881 to master December 12, 2025 17:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants