One of the biggest misconceptions about MongoDB (and NoSQL databases in general) is that they cannot handle relationships. People mistakenly believe that because it isn’t a “Relational Database,” you cannot connect data together.
This is entirely false. MongoDB handles relationships brilliantly; it just does it differently than SQL.
In SQL, you always split related data into separate tables and connect them using Foreign Keys and JOINs. In MongoDB, you have a choice: you can either Embed the data directly inside the document, or you can Reference it (similar to SQL).
Let’s explore how to model the three standard types of relationships: One-to-One, One-to-Many, and Many-to-Many.
1. Embedded Documents
The most “MongoDB-native” way to handle relationships is by Embedding. Because BSON allows for deeply nested Objects and Arrays, you can simply store the related data directly inside the parent document.
Pros of Embedding:
- Speed: You can retrieve an entity and all of its related data in a single database read operation.
- Simplicity: No need for complex
$lookupaggregations or secondary queries.
Cons of Embedding:
- Data Duplication: If the embedded data needs to be shared across many different documents, you will end up duplicating it.
- Document Size Limit: A single MongoDB document cannot exceed 16MB. If an array of embedded documents grows infinitely (e.g., thousands of comments on a viral post), you will hit this hard limit.
2. Referenced Documents
Referencing (or “Normalization”) is the SQL way of doing things. Instead of storing the related data inside the document, you store the _id (the reference) to a document that lives in a completely different collection.
Pros of Referencing:
- No Duplication: Data exists in only one place, making updates highly efficient.
- No Size Limits: Because you are only storing an array of small ObjectIds, you won’t hit the 16MB document limit.
Cons of Referencing:
- Slower Reads: To get all the data, you either have to perform multiple separate queries or use a
$lookupaggregation, which requires more CPU overhead.
3. Modeling a One-to-One Relationship
A One-to-One relationship occurs when one entity belongs to exactly one other entity. Example: A User has one Profile.
Best Practice: Embed the Data. Unless the profile data is massive and rarely accessed, there is almost no reason to separate them in MongoDB.
// One-to-One (Embedded)
{
"_id": ObjectId("..."),
"username": "suresh_coder",
"email": "suresh@example.com",
"profile": {
"fullName": "Suresh S",
"avatarUrl": "/images/suresh.jpg",
"bio": "Full stack developer."
}
}
4. Modeling a One-to-Many Relationship
A One-to-Many relationship occurs when one entity owns multiple sub-entities. This is where you have to make a choice between Embedding and Referencing based on the “Many” part.
Scenario A: One-to-Few (Embed)
Example: A User has a few Addresses (Home, Work, Billing).
Because a user will rarely have more than 5 or 6 addresses, it is perfectly safe and highly efficient to embed them as an array.
{
"username": "suresh_coder",
"addresses": [
{ "type": "Home", "street": "123 Main St" },
{ "type": "Work", "street": "456 Office Blvd" }
]
}
Scenario B: One-to-Many (Reference the Parent)
Example: A Blog Post has thousands of Comments.
If a post goes viral, embedding thousands of large comment objects could hit the 16MB limit and cause memory issues when fetching the post. Instead, we use Referencing. We place the _id of the Post inside the Comment.
The Post Document (in the posts collection):
{
"_id": ObjectId("POST_123"),
"title": "Learning MongoDB",
"content": "..."
}
The Comment Document (in the comments collection):
{
"_id": ObjectId("..."),
"postId": ObjectId("POST_123"), // The Reference!
"author": "Alice",
"text": "Great article!"
}
(To get all comments for the post, you simply query db.comments.find({ postId: ObjectId("POST_123") })).
5. Modeling a Many-to-Many Relationship
A Many-to-Many relationship occurs when multiple entities can be linked to multiple other entities. Example: Students and Courses. A Student takes many Courses, and a Course has many Students.
In SQL, you must create a third “Join Table” to handle this. In MongoDB, you simply use arrays of ObjectIds in both collections!
The Student Document:
{
"_id": ObjectId("STUDENT_1"),
"name": "John Doe",
"enrolledCourses": [
ObjectId("COURSE_101"),
ObjectId("COURSE_202")
]
}
The Course Document:
{
"_id": ObjectId("COURSE_101"),
"title": "Database Architecture",
"enrolledStudents": [
ObjectId("STUDENT_1"),
ObjectId("STUDENT_99")
]
}
Choosing the Right Relationship Strategy
Deciding whether to Embed or Reference is the hardest part of MongoDB schema design. Ask yourself these three questions:
- Does the data frequently change?
- If the related data changes constantly (like the price of a stock), Reference it. Otherwise, you would have to update thousands of embedded documents every time the price changed.
- Will the array grow indefinitely?
- If the “many” side of a relationship will keep growing forever (like a server’s log events), Reference it. If the list is small and bounded (like a user’s phone numbers), Embed it.
- Do you always need the data together?
- If your application always displays the related data whenever it shows the parent (like an invoice and its line items), Embed it to save the database an extra read operation.
Summary
MongoDB is not relationship-agnostic; it simply gives you the freedom to optimize your relationships for your specific application’s read/write patterns.
- Use Embedded Documents for One-to-One and One-to-Few relationships to maximize read performance.
- Use Referenced Documents for large One-to-Many and Many-to-Many relationships to avoid data duplication and prevent hitting the 16MB document size limit.
In Module 11, we will expand on these concepts and look at broader Schema Design Principles!
Discussion
Loading comments...