乐闻世界logo
搜索文章和话题

How to create and use Mongoose plugins?

2月22日 20:12

Mongoose Plugins are a reusable mechanism for extending Mongoose Schema functionality. Plugins allow you to encapsulate common functionality and reuse it across multiple Schemas.

Basic Plugins

Creating Simple Plugin

javascript
// timestamp.js function timestampPlugin(schema) { schema.add({ createdAt: { type: Date, default: Date.now }, updatedAt: { type: Date, default: Date.now } }); schema.pre('save', function(next) { this.updatedAt = new Date(); next(); }); } module.exports = timestampPlugin;

Using Plugin

javascript
const timestampPlugin = require('./plugins/timestamp'); const userSchema = new Schema({ name: String, email: String }); userSchema.plugin(timestampPlugin); const User = mongoose.model('User', userSchema);

Plugin Options

Plugin with Options

javascript
// softDelete.js function softDeletePlugin(schema, options = {}) { const deletedAtField = options.deletedAtField || 'deletedAt'; const isDeletedField = options.isDeletedField || 'isDeleted'; schema.add({ [deletedAtField]: Date, [isDeletedField]: { type: Boolean, default: false } }); schema.pre('find', function() { this.where({ [isDeletedField]: { $ne: true } }); }); schema.pre('findOne', function() { this.where({ [isDeletedField]: { $ne: true } }); }); schema.methods.softDelete = function() { this[isDeletedField] = true; this[deletedAtField] = new Date(); return this.save(); }; } module.exports = softDeletePlugin; // Use plugin with options userSchema.plugin(softDeletePlugin, { deletedAtField: 'deletedAt', isDeletedField: 'isDeleted' });

Common Plugin Types

1. Timestamp Plugin

javascript
function timestampPlugin(schema) { schema.add({ createdAt: { type: Date, default: Date.now }, updatedAt: { type: Date, default: Date.now } }); schema.pre('save', function(next) { this.updatedAt = new Date(); next(); }); }

2. Soft Delete Plugin

javascript
function softDeletePlugin(schema) { schema.add({ deletedAt: Date, isDeleted: { type: Boolean, default: false } }); schema.pre('find', function() { this.where({ isDeleted: { $ne: true } }); }); schema.pre('findOne', function() { this.where({ isDeleted: { $ne: true } }); }); schema.methods.softDelete = function() { this.isDeleted = true; this.deletedAt = new Date(); return this.save(); }; schema.statics.findDeleted = function() { return this.find({ isDeleted: true }); }; }

3. Pagination Plugin

javascript
function paginatePlugin(schema) { schema.statics.paginate = function(query = {}, options = {}) { const { page = 1, limit = 10, sort = {}, populate = [] } = options; const skip = (page - 1) * limit; return Promise.all([ this.countDocuments(query), this.find(query) .sort(sort) .skip(skip) .limit(limit) .populate(populate) ]).then(([total, docs]) => ({ docs, total, page, pages: Math.ceil(total / limit) })); }; } // Use pagination plugin const result = await User.paginate( { status: 'active' }, { page: 1, limit: 10, sort: { name: 1 } } );

4. Auto Populate Plugin

javascript
function autoPopulatePlugin(schema, options) { const fields = options.fields || []; schema.pre('find', function() { fields.forEach(field => { this.populate(field); }); }); schema.pre('findOne', function() { fields.forEach(field => { this.populate(field); }); }); } // Use auto populate plugin userSchema.plugin(autoPopulatePlugin, { fields: ['profile', 'settings'] });

5. Encryption Plugin

javascript
const bcrypt = require('bcrypt'); function encryptionPlugin(schema, options) { const fields = options.fields || ['password']; schema.pre('save', async function(next) { for (const field of fields) { if (this.isModified(field)) { this[field] = await bcrypt.hash(this[field], 10); } } next(); }); schema.methods.comparePassword = async function(candidatePassword) { return bcrypt.compare(candidatePassword, this.password); }; } // Use encryption plugin userSchema.plugin(encryptionPlugin, { fields: ['password'] });

Plugin Composition

Multiple Plugins

javascript
const timestampPlugin = require('./plugins/timestamp'); const softDeletePlugin = require('./plugins/softDelete'); const paginatePlugin = require('./plugins/paginate'); userSchema.plugin(timestampPlugin); userSchema.plugin(softDeletePlugin); userSchema.plugin(paginatePlugin);

Plugin Dependencies

javascript
function advancedPlugin(schema, options) { // Depend on functionality from other plugins if (!schema.path('createdAt')) { throw new Error('timestampPlugin must be applied before advancedPlugin'); } // Use fields added by other plugins schema.virtual('age').get(function() { return Date.now() - this.createdAt.getTime(); }); }

Global Plugins

Apply to All Schemas

javascript
// Apply plugin globally mongoose.plugin(timestampPlugin); // All Schemas created after will automatically apply the plugin const userSchema = new Schema({ name: String }); const postSchema = new Schema({ title: String }); // Both Schemas will have createdAt and updatedAt fields

Conditional Global Plugins

javascript
// Apply plugin only to specific models mongoose.plugin(function(schema) { if (schema.options.enableTimestamps) { schema.add({ createdAt: { type: Date, default: Date.now }, updatedAt: { type: Date, default: Date.now } }); } }); // Enable in Schema options const userSchema = new Schema({ name: String }, { enableTimestamps: true });

Plugin Best Practices

1. Plugin Naming

javascript
// Use clear naming const timestampPlugin = require('./plugins/timestamp'); const softDeletePlugin = require('./plugins/softDelete');

2. Plugin Documentation

javascript
/** * Soft Delete Plugin * * Features: * - Add deletedAt and isDeleted fields * - Automatically filter deleted documents * - Provide softDelete() method * * Options: * - deletedAtField: Deleted at field name (default: deletedAt) * - isDeletedField: Deleted flag field name (default: isDeleted) */ function softDeletePlugin(schema, options = {}) { // Plugin implementation }

3. Plugin Testing

javascript
// plugins/softDelete.test.js const mongoose = require('mongoose'); const softDeletePlugin = require('./softDelete'); describe('softDeletePlugin', () => { let User; beforeAll(async () => { await mongoose.connect('mongodb://localhost/test'); const userSchema = new Schema({ name: String }); userSchema.plugin(softDeletePlugin); User = mongoose.model('User', userSchema); }); it('should add soft delete fields', async () => { const user = await User.create({ name: 'John' }); expect(user.isDeleted).toBe(false); expect(user.deletedAt).toBeUndefined(); }); it('should filter deleted documents', async () => { const user = await User.create({ name: 'Jane' }); await user.softDelete(); const users = await User.find(); expect(users.length).toBe(0); }); });

Publishing Plugins

NPM Package Structure

shell
mongoose-plugin-softdelete/ ├── package.json ├── README.md ├── index.js └── test/ └── softDelete.test.js

package.json

json
{ "name": "mongoose-plugin-softdelete", "version": "1.0.0", "description": "Mongoose plugin for soft delete functionality", "main": "index.js", "keywords": ["mongoose", "plugin", "soft-delete"], "peerDependencies": { "mongoose": ">=6.0.0" } }

Best Practices

  1. Keep plugins simple: Each plugin should do one thing
  2. Provide options: Allow users to customize plugin behavior
  3. Complete documentation: Provide clear documentation and examples
  4. Test coverage: Write complete tests for plugins
  5. Version management: Use semantic versioning
  6. Error handling: Handle error situations properly
  7. Performance considerations: Avoid plugins affecting performance
  8. Backward compatibility: Maintain API backward compatibility
标签:Mongoose