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

How to use Mongoose subdocuments and what are their use cases?

2月22日 20:12

Mongoose Subdocuments are documents nested within parent documents. They can be single documents or arrays of documents. Subdocuments provide a way to organize related data while maintaining data integrity.

Subdocument Types

1. Nested Schema (Single Subdocument)

javascript
const addressSchema = new Schema({ street: String, city: String, state: String, zipCode: String }); const userSchema = new Schema({ name: String, email: String, address: addressSchema // Single subdocument }); const User = mongoose.model('User', userSchema); // Create user with subdocument const user = await User.create({ name: 'John Doe', email: 'john@example.com', address: { street: '123 Main St', city: 'New York', state: 'NY', zipCode: '10001' } });

2. Subdocument Arrays

javascript
const commentSchema = new Schema({ text: String, author: String, createdAt: { type: Date, default: Date.now } }); const postSchema = new Schema({ title: String, content: String, comments: [commentSchema] // Subdocument array }); const Post = mongoose.model('Post', postSchema); // Create post with subdocument array const post = await Post.create({ title: 'My First Post', content: 'This is my first post', comments: [ { text: 'Great post!', author: 'Alice' }, { text: 'Thanks for sharing', author: 'Bob' } ] });

Subdocument Operations

Accessing Subdocuments

javascript
// Access single subdocument const user = await User.findById(userId); console.log(user.address.city); // "New York" // Access subdocument array const post = await Post.findById(postId); console.log(post.comments[0].text); // "Great post!"

Modifying Subdocuments

javascript
// Modify single subdocument const user = await User.findById(userId); user.address.city = 'Los Angeles'; await user.save(); // Modify subdocument array element const post = await Post.findById(postId); post.comments[0].text = 'Updated comment'; await post.save();

Adding Subdocuments to Array

javascript
// Add new comment const post = await Post.findById(postId); post.comments.push({ text: 'New comment', author: 'Charlie' }); await post.save(); // Use unshift to add to beginning post.comments.unshift({ text: 'First comment', author: 'Dave' }); await post.save();

Deleting Subdocuments

javascript
// Delete subdocument from array const post = await Post.findById(postId); post.comments.splice(1, 1); // Delete second comment await post.save(); // Use pull to delete matching subdocuments post.comments.pull({ author: 'Alice' }); await post.save();

Subdocument Middleware

Subdocument-level Middleware

javascript
commentSchema.pre('save', function(next) { console.log('Saving comment:', this.text); next(); }); commentSchema.post('save', function(doc) { console.log('Comment saved:', doc.text); });

Parent Document Middleware

javascript
postSchema.pre('save', function(next) { console.log('Saving post with', this.comments.length, 'comments'); next(); });

Subdocument Validation

Subdocument-level Validation

javascript
const addressSchema = new Schema({ street: { type: String, required: true }, city: { type: String, required: true }, state: { type: String, required: true, minlength: 2 }, zipCode: { type: String, required: true, match: /^\d{5}$/ } }); // Validation triggers automatically when saving parent document try { const user = await User.create({ name: 'John', email: 'john@example.com', address: { street: '123 Main St', city: 'New York', state: 'NY', zipCode: '10001' } }); } catch (error) { console.error('Validation error:', error.message); }

Subdocument Methods

Adding Methods to Subdocuments

javascript
commentSchema.methods.getFormattedDate = function() { return this.createdAt.toLocaleDateString(); }; const post = await Post.findById(postId); console.log(post.comments[0].getFormattedDate());

Adding Static Methods to Subdocuments

javascript
commentSchema.statics.findByAuthor = function(author) { return this.find({ author }); }; // Note: Subdocument static methods are typically not used directly // More common to define methods on parent document to manipulate subdocuments

Subdocument References

Using ObjectId References

javascript
const postSchema = new Schema({ title: String, content: String, comments: [{ type: Schema.Types.ObjectId, ref: 'Comment' }] }); const commentSchema = new Schema({ text: String, author: String }); // Use populate to get complete comments const post = await Post.findById(postId).populate('comments');

Subdocuments vs References

Selection Guide

Use subdocuments when:

  • Data is always accessed with parent document
  • Subdocument count is limited and relatively small
  • Atomic updates are needed
  • Data doesn't need independent querying

Use references when:

  • Subdocuments may be accessed independently
  • Subdocument count may be large
  • Cross-document querying is needed
  • Better performance is needed
javascript
// Subdocument example - suitable for few comments const postSchema = new Schema({ title: String, comments: [commentSchema] }); // Reference example - suitable for many comments const postSchema = new Schema({ title: String, comments: [{ type: Schema.Types.ObjectId, ref: 'Comment' }] });

Advanced Usage

Subdocument Array Operations

javascript
// Use $push to add elements await Post.findByIdAndUpdate(postId, { $push: { comments: { $each: [ { text: 'Comment 1', author: 'User1' }, { text: 'Comment 2', author: 'User2' } ], $position: 0 // Add to beginning } } }); // Use $pull to delete elements await Post.findByIdAndUpdate(postId, { $pull: { comments: { author: 'User1' } } }); // Use $set to update specific element await Post.updateOne( { _id: postId, 'comments._id': commentId }, { $set: { 'comments.$.text': 'Updated text' } } );

Subdocument Validators

javascript
const postSchema = new Schema({ title: String, comments: [commentSchema] }); // Custom validator postSchema.path('comments').validate(function(comments) { return comments.length <= 100; }, 'Maximum 100 comments allowed'); // Validate subdocument properties postSchema.path('comments').validate(function(comments) { return comments.every(comment => comment.text.length > 0); }, 'All comments must have text');

Best Practices

  1. Choose structure wisely: Select subdocuments or references based on access patterns
  2. Limit array size: Avoid overly large subdocument arrays
  3. Use validation: Add appropriate validation rules for subdocuments
  4. Consider performance: Large subdocument arrays may affect performance
  5. Use middleware: Leverage middleware to handle subdocument logic
  6. Clear documentation: Add clear comments to subdocument schemas
  7. Test coverage: Write tests for subdocument operations
标签:Mongoose