Chapter 6 of 13
~12 minRelationships: Embedding vs. Referencing
MongoDB gives you two fundamentally different ways to model a relationship between two pieces of data: embedding one document inside another, or storing a reference and looking it up separately. Choosing correctly for each relationship is one of the most consequential decisions in a MongoDB schema.
Embedding: Storing Related Data Together
Embedding nests one document (or an array of documents) directly inside its parent. It is the natural choice when the embedded data is only ever accessed in the context of its parent, does not grow unboundedly, and does not need to be queried or updated independently very often. The upside is a single read retrieves everything you need; the downside is the 16MB document size limit and the fact that deeply nested arrays that grow without bound become slow to update and can eventually hit that limit.
const user = { _id: ObjectId("..."), name: "Ada Lovelace", addresses: [ { type: "home", city: "London", zip: "SW1A 1AA" }, { type: "work", city: "Cambridge", zip: "CB2 1TN" } ] };
Referencing: Storing a Pointer Instead
Referencing stores just the related document's _id (or another identifier) and requires a separate query - either a manual second find(), or a $lookup aggregation stage, or Mongoose's populate() - to fetch the full related document. Referencing is the right call when the related data is large, changes independently, is shared across many parents (so duplicating it would multiply update work), or grows without a natural bound.
// orders collection references products rather than embedding // full product details, since products are shared, updated // independently, and can be large. const order = { _id: ObjectId("order1"), userId: ObjectId("user1"), items: [ { productId: ObjectId("prod1"), quantity: 2 }, { productId: ObjectId("prod2"), quantity: 1 } ] };
One-to-Few, One-to-Many, and Many-to-Many
One-to-few relationships (a user with a handful of addresses) are usually embedded directly, since the array stays small and bounded. One-to-many relationships where the "many" side can grow large or unbounded (a blog with thousands of comments across all posts, or a customer with years of orders) are usually referenced, often storing the reference on the "many" side (each comment stores a postId) rather than as a growing array on the "one" side. Many-to-many relationships (students and courses, where each belongs to several of the other) are almost always modeled with references on both sides, or with a small join-style collection when the relationship itself carries extra data (like an enrollment date).
// Instead of an ever-growing array of comments inside a post // (risking hitting the 16MB limit on a popular post), store // each comment as its own document referencing the post: db.comments.insertOne({ postId: ObjectId("post1"), author: "Grace", text: "Nice explanation!" }) db.comments.find({ postId: ObjectId("post1") })
Populate: Resolving References in Mongoose
When using Mongoose, a referenced field is typically declared with a `ref` to another model, and `.populate()` replaces the stored _id with the full referenced document at query time - roughly equivalent to a client-side join. It is convenient, but it is worth remembering it issues an additional query behind the scenes, so populating deeply nested or very large result sets can add real latency if used carelessly.
Coding challenge
You are modeling a university system: students can enroll in many courses, and each course has many students. Describe how you would model this many-to-many relationship in MongoDB and why embedding is a poor fit here.
// Write your solution here
Answer every question below to mark this chapter as complete.
Quiz Yourself
Score: 0/41. When is embedding generally the better choice over referencing?
2. Why is storing an ever-growing array of comments directly inside a blog post document risky?
3. What does Mongoose's .populate() do?
4. What is a common way to model a many-to-many relationship, like students and courses, in MongoDB?