Embedding Related Data
Objectives
By the end of this lesson, you should be able to:
- Embed related data directly inside a Mongoose document
- Define a schema for an array of embedded subdocuments
- Explain the trade-offs embedding introduces
💡 Why this matters: Module 7 previewed nesting data directly inside a document. This lesson covers doing that properly, with Mongoose, for a genuine one-to-many relationship, no separate collection at all.
⚠️ A note on verification: this tooling has no route to a local MongoDB server. The code below reflects Mongoose’s stable, current, documented API. Try it yourself, on your own machine, against a real MongoDB server.
The Relationship
A blog post has many comments. In PostgreSQL (Module 5), this would be a comments table with a foreign key back to posts. In MongoDB, it can instead be embedded, comments stored directly inside the post document itself.
Defining an Embedded Schema
const commentSchema = new mongoose.Schema({
author: { type: String, required: true },
text: { type: String, required: true },
postedAt: { type: Date, default: Date.now }
});
const postSchema = new mongoose.Schema({
title: { type: String, required: true },
body: { type: String, required: true },
comments: [commentSchema]
});
const Post = mongoose.model('Post', postSchema);
comments: [commentSchema] embeds an array of comment subdocuments directly inside every post. commentSchema doesn’t get its own mongoose.model() call, it’s never a standalone collection, only ever a piece of a Post document.
Creating a Post with Embedded Comments
const post = await Post.create({
title: 'Understanding MongoDB',
body: 'Documents, collections, and everything in between.',
comments: [
{ author: 'Erin', text: 'Great post!' },
{ author: 'Jordan', text: 'This cleared things up, thanks.' }
]
});
console.log(post);
{
_id: new ObjectId('...'),
title: 'Understanding MongoDB',
body: 'Documents, collections, and everything in between.',
comments: [
{ author: 'Erin', text: 'Great post!', _id: new ObjectId('...'), postedAt: ... },
{ author: 'Jordan', text: 'This cleared things up, thanks.', _id: new ObjectId('...'), postedAt: ... }
],
__v: 0
}
One document, one query, the entire post and every comment on it, no JOIN, no second collection to query.
Adding a Comment to an Existing Post
const post = await Post.findById(postId);
post.comments.push({ author: 'Maya', text: 'Nice explanation of embedding.' });
await post.save();
comments.push(...) adds to the in-memory array, .save() writes the whole updated document back, comments are just part of the post’s own data.
Why Embedding Fits Here
- Comments are always read together with their post, never independently.
- Comments belong exclusively to one post, they’re never shared or reused elsewhere.
- The whole post, with every comment, loads in a single query, no extra round trip needed.
The Trade-Off
Embedding works well while the embedded array stays reasonably small. A wildly popular post with tens of thousands of comments would make every fetch of that post pull all of them along with it, whether they’re needed or not, and MongoDB documents have a hard 16MB size limit. This is exactly the trade-off the next lesson’s alternative, referencing, exists to solve.
Try It
- Define a
Postschema with an embeddedcommentsarray, as shown above. - Create a post with two embedded comments in a single
create()call. - Fetch the post back, and confirm both comments are nested inside it, no separate query needed.
- Add a third comment to an existing post using
.push()and.save().
Recap
- Embedding stores related data directly inside a parent document, using an array of subdocuments defined with their own (non-model) schema.
- It fits data that’s always read together, exclusively belongs to one parent, and stays reasonably bounded in size.
- A single query loads the parent and everything embedded in it, at the cost of always loading all of it, whether needed or not.
Next lesson: referencing, MongoDB’s alternative for relationships that don’t fit embedding.