-
-
Notifications
You must be signed in to change notification settings - Fork 4k
feat: add option to skip middleware #15883
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
…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`.
There was a problem hiding this 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
middlewareoption acceptingfalseor{ pre?: boolean, post?: boolean }to skip hooks - Moved validation logic from
validateBeforeSavepre-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.
hasezoey
left a comment
There was a problem hiding this 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) { |
There was a problem hiding this comment.
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.
|
Note to self: function buildPreSavePromise(document, options) {
return new Promise((resolve, reject) => {
document.schema.s.hooks.execPre('save', document, [options], (err) => {
if (err) {
reject(err);
return;
}
resolve();
});
});
} |
Summary
This PR adds a
middlewareoption 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:
Existing workarounds required manual effort:
this.getOptions().disableMiddlewares)Model.collection.findOne()which bypasses Mongoose entirelyModel.bulkWrite()for save operations (skips all middleware)This PR provides a first-class option that works consistently across all operations.
The Solution
The new
middlewareoption accepts three forms:Key Implementation Details
Supported operations:
find,findOne,findOneAndUpdate,findOneAndDelete,findOneAndReplace,updateOne,updateMany,deleteOne,deleteMany,countDocuments,replaceOne,save,validate,insertMany,bulkWrite,aggregate, and document-levelupdateOne.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
middlewareoption onsave()only skips save hooks - validation still runs. This is intentional because validation is a safety feature. To skip validation, use the existingvalidateBeforeSave: falseoption.To support this, I moved validation logic out of the
validateBeforeSaveplugin (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
validateBeforeSavewas 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