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)
javascriptconst 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
javascriptconst 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
javascriptcommentSchema.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
javascriptpostSchema.pre('save', function(next) { console.log('Saving post with', this.comments.length, 'comments'); next(); });
Subdocument Validation
Subdocument-level Validation
javascriptconst 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
javascriptcommentSchema.methods.getFormattedDate = function() { return this.createdAt.toLocaleDateString(); }; const post = await Post.findById(postId); console.log(post.comments[0].getFormattedDate());
Adding Static Methods to Subdocuments
javascriptcommentSchema.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
javascriptconst 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
javascriptconst 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
- Choose structure wisely: Select subdocuments or references based on access patterns
- Limit array size: Avoid overly large subdocument arrays
- Use validation: Add appropriate validation rules for subdocuments
- Consider performance: Large subdocument arrays may affect performance
- Use middleware: Leverage middleware to handle subdocument logic
- Clear documentation: Add clear comments to subdocument schemas
- Test coverage: Write tests for subdocument operations