Mongoose provides instance methods and static methods as two ways to extend model functionality. Understanding the differences and use cases of these two methods is crucial for writing maintainable code.
Instance Methods
Instance methods are added to document instances and can be called on individual documents. These methods can access the this keyword to reference the current document.
Defining Instance Methods
javascriptconst userSchema = new Schema({ firstName: String, lastName: String, email: String, password: String, createdAt: { type: Date, default: Date.now } }); // Add instance method userSchema.methods.getFullName = function() { return `${this.firstName} ${this.lastName}`; }; userSchema.methods.isNewUser = function() { const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000); return this.createdAt > oneDayAgo; }; userSchema.methods.comparePassword = function(candidatePassword) { // Use bcrypt to compare passwords return bcrypt.compare(candidatePassword, this.password); }; const User = mongoose.model('User', userSchema); // Use instance method const user = await User.findById(userId); console.log(user.getFullName()); // "John Doe" console.log(user.isNewUser()); // true/false const isMatch = await user.comparePassword('password123');
Instance Method Use Cases
- Document-specific operations: Perform operations on single documents
- Data validation: Validate document data
- Data transformation: Convert document data formats
- Business logic: Encapsulate business logic
- Status checking: Check document status
javascript// Example: Order instance methods const orderSchema = new Schema({ items: [{ product: { type: Schema.Types.ObjectId, ref: 'Product' }, quantity: Number, price: Number }], status: { type: String, enum: ['pending', 'paid', 'shipped', 'delivered', 'cancelled'] }, createdAt: { type: Date, default: Date.now } }); orderSchema.methods.getTotalPrice = function() { return this.items.reduce((sum, item) => { return sum + (item.price * item.quantity); }, 0); }; orderSchema.methods.canBeCancelled = function() { return ['pending', 'paid'].includes(this.status); }; orderSchema.methods.markAsShipped = function() { if (this.status !== 'paid') { throw new Error('Order must be paid before shipping'); } this.status = 'shipped'; return this.save(); };
Static Methods
Static methods are added to the model class and can be called directly on the model without instantiating documents. These methods are typically used for queries or batch operations.
Defining Static Methods
javascript// Add static method userSchema.statics.findByEmail = function(email) { return this.findOne({ email }); }; userSchema.statics.getActiveUsers = function() { return this.find({ status: 'active' }); }; userSchema.statics.countByStatus = function(status) { return this.countDocuments({ status }); }; userSchema.statics.findAdultUsers = function() { return this.find({ age: { $gte: 18 } }); }; // Use static method const user = await User.findByEmail('john@example.com'); const activeUsers = await User.getActiveUsers(); const activeCount = await User.countByStatus('active'); const adultUsers = await User.findAdultUsers();
Static Method Use Cases
- Query operations: Encapsulate common queries
- Batch operations: Execute batch updates or deletions
- Statistical operations: Calculate statistical data
- Business rules: Implement business rule queries
- Complex queries: Encapsulate complex query logic
javascript// Example: Product static methods const productSchema = new Schema({ name: String, price: Number, category: String, stock: Number, active: { type: Boolean, default: true } }); productSchema.statics.findByCategory = function(category) { return this.find({ category, active: true }); }; productSchema.statics.findInPriceRange = function(min, max) { return this.find({ price: { $gte: min, $lte: max }, active: true }); }; productSchema.statics.findLowStock = function(threshold = 10) { return this.find({ stock: { $lte: threshold }, active: true }); }; productSchema.statics.updateStock = function(productId, quantity) { return this.findByIdAndUpdate( productId, { $inc: { stock: quantity } }, { new: true } ); };
Instance Methods vs Static Methods
Comparison
| Feature | Instance Methods | Static Methods |
|---|---|---|
| Call method | document.method() | Model.method() |
| Access this | Can access document instance | Cannot access document instance |
| Use cases | Single document operations | Queries and batch operations |
| Definition location | schema.methods | schema.statics |
| Return value | Usually returns document or modified value | Usually returns query results |
Selection Guide
Use instance methods when:
- Need to operate on a single document
- Need to access document properties
- Method is related to a specific document
- Need to modify document state
Use static methods when:
- Need to query multiple documents
- Need to perform batch operations
- Method is related to document collection
- Don't need to access a specific document
Advanced Usage
Async Methods
javascript// Async instance method userSchema.methods.sendWelcomeEmail = async function() { const emailService = require('./services/email'); await emailService.send({ to: this.email, subject: 'Welcome!', body: `Hello ${this.firstName}!` }); return this; }; // Async static method userSchema.statics.sendNewsletter = async function(subject, content) { const users = await this.find({ subscribed: true }); const emailService = require('./services/email'); for (const user of users) { await emailService.send({ to: user.email, subject, body: content }); } return users.length; };
Chaining
javascript// Static method returns query builder userSchema.statics.queryActive = function() { return this.find({ active: true }); }; // Use chaining const users = await User.queryActive() .select('name email') .sort({ name: 1 }) .limit(10);
Combined Usage
javascript// Static method queries, instance method processes const users = await User.findByEmail('john@example.com'); if (user) { await user.sendWelcomeEmail(); }
Best Practices
- Clear naming: Use descriptive method names
- Single responsibility: Each method should do one thing
- Error handling: Handle error situations properly
- Documentation: Add clear comments for methods
- Type safety: Use TypeScript or JSDoc
- Test coverage: Write tests for custom methods
- Avoid duplication: Don't duplicate existing Mongoose methods
- Performance considerations: Be aware of method performance impact