MongoDB Relationships
In any real-world application, data is rarely isolated. A customer places orders. An order contains products. A product belongs to a category. These connections between data entities are called relationships. MongoDB handles relationships differently from SQL databases — instead of joining tables through foreign keys automatically, MongoDB gives control over how related data is stored and retrieved.
There are three main types of data relationships: one-to-one, one-to-many, and many-to-many. Each can be implemented using embedding or referencing in MongoDB.
One-to-One Relationship
A one-to-one relationship means one entity is associated with exactly one other entity — and vice versa. For example, one employee has one employee ID card. One user has one profile.
Implementation: Embedding (Recommended)
Since the data is tightly related and always accessed together, embedding the sub-document is the most efficient approach.
db.employees.insertOne({
name: "Kavya Menon",
designation: "Software Engineer",
idCard: {
cardNumber: "EMP-2025-001",
issuedDate: ISODate("2023-06-01"),
expiryDate: ISODate("2028-06-01")
}
})
Fetching the employee also returns the ID card details in one single query.
Retrieving One-to-One Data
db.employees.findOne({ name: "Kavya Menon" })
The full document — including embedded ID card — returns in one call with no join required.
One-to-Many Relationship
A one-to-many relationship means one entity is associated with multiple others. A customer places many orders. A blog post has many comments. A department has many employees.
The implementation choice depends on how many "many" records exist and whether they are needed alongside the parent every time.
Option A: Embedding (One-to-Few)
When the number of related items is small and predictable, embedding works well.
db.blogPosts.insertOne({
title: "Getting Started with MongoDB",
author: "Rohit Sharma",
comments: [
{ user: "Anil", text: "Great article!", date: ISODate("2024-12-01") },
{ user: "Sunita", text: "Very helpful, thanks.", date: ISODate("2024-12-02") }
]
})
Comments are embedded because they are always read together with the post and a post typically has a manageable number of them.
Option B: Referencing (One-to-Many at Scale)
When there are potentially thousands of related documents, referencing avoids document size problems.
// customers collection
db.customers.insertOne({
_id: ObjectId("c001"),
name: "Divya Iyer",
email: "divya@example.com"
})
// orders collection — each order references its customer
db.orders.insertMany([
{ orderId: 1001, customerId: ObjectId("c001"), product: "Laptop", amount: 55000 },
{ orderId: 1002, customerId: ObjectId("c001"), product: "Mouse", amount: 850 },
{ orderId: 1003, customerId: ObjectId("c001"), product: "Keyboard", amount: 1200 }
])
The customer document stays lean. Each order holds the customer's ID to establish the relationship.
Querying Referenced One-to-Many Data
To find all orders for a specific customer:
db.orders.find({ customerId: ObjectId("c001") })
To combine customer and order data using the aggregation pipeline:
db.customers.aggregate([
{ $match: { _id: ObjectId("c001") } },
{
$lookup: {
from: "orders",
localField: "_id",
foreignField: "customerId",
as: "orderHistory"
}
}
])
Result:
{
"_id": ObjectId("c001"),
"name": "Divya Iyer",
"email": "divya@example.com",
"orderHistory": [
{ "orderId": 1001, "product": "Laptop", "amount": 55000 },
{ "orderId": 1002, "product": "Mouse", "amount": 850 },
{ "orderId": 1003, "product": "Keyboard", "amount": 1200 }
]
}
Many-to-Many Relationship
A many-to-many relationship means one entity can be associated with many others, and those others can also be associated with many entities. A student can enroll in many courses, and a course can have many students enrolled.
Implementation: Array of References (Recommended)
// students collection
db.students.insertMany([
{ _id: ObjectId("s001"), name: "Harshita Singh", enrolledCourseIds: [ObjectId("co001"), ObjectId("co002")] },
{ _id: ObjectId("s002"), name: "Tanmay Ghosh", enrolledCourseIds: [ObjectId("co001"), ObjectId("co003")] }
])
// courses collection
db.courses.insertMany([
{ _id: ObjectId("co001"), courseName: "Web Development", instructor: "Ms. Leela" },
{ _id: ObjectId("co002"), courseName: "Data Science", instructor: "Mr. Arora" },
{ _id: ObjectId("co003"), courseName: "Cloud Computing", instructor: "Dr. Singh" }
])
Each student stores an array of course IDs. No course data is duplicated — if a course name changes, it is updated in one place only.
Finding All Courses for a Student
db.students.aggregate([
{ $match: { name: "Harshita Singh" } },
{
$lookup: {
from: "courses",
localField: "enrolledCourseIds",
foreignField: "_id",
as: "courses"
}
},
{ $project: { name: 1, courses: 1, _id: 0 } }
])
Result:
{
"name": "Harshita Singh",
"courses": [
{ "courseName": "Web Development", "instructor": "Ms. Leela" },
{ "courseName": "Data Science", "instructor": "Mr. Arora" }
]
}
Finding All Students Enrolled in a Course
db.students.find({ enrolledCourseIds: ObjectId("co001") }, { name: 1, _id: 0 })
MongoDB checks if ObjectId("co001") exists inside the enrolledCourseIds array and returns all matching students.
Self-Referencing Relationships
Some data points back to the same collection. For example, an employee can have a manager who is also an employee in the same collection.
db.staff.insertMany([
{ _id: ObjectId("e001"), name: "Rajan Bhat", role: "Manager", managerId: null },
{ _id: ObjectId("e002"), name: "Sonal Kapoor", role: "Developer", managerId: ObjectId("e001") },
{ _id: ObjectId("e003"), name: "Vikas Jain", role: "Designer", managerId: ObjectId("e001") }
])
To find all team members reporting to Rajan Bhat:
db.staff.find({ managerId: ObjectId("e001") })
Relationship Design — Decision Table
| Relationship | Related Items | Approach | Reason |
|---|---|---|---|
| One-to-One | Always with parent | Embed | Single read, tight coupling |
| One-to-Few | Small, bounded list | Embed | All data in one document |
| One-to-Many | Large or unbounded | Reference | Avoid document size bloat |
| Many-to-Many | Both sides large | Reference with ID arrays | Avoid data duplication |
| Self-referencing | Same collection | Reference (ID field) | Parent-child within one collection |
Avoiding Common Relationship Mistakes
Mistake 1 — Embedding Unbounded Arrays
Embedding an array that grows without limit (like all messages in a chat group inside the group document) will eventually exceed MongoDB's 16 MB document size limit. Reference instead.
Mistake 2 — Referencing Everything (Over-normalizing)
Referencing every piece of data, even tightly coupled data like an order's shipping address, forces multiple queries on every page load. Embed data that belongs together and rarely changes.
Mistake 3 — Duplicating Data Without a Plan
Sometimes duplicating data (denormalization) is intentional and beneficial in MongoDB. For example, storing the customer's name inside an order document avoids a lookup for displaying order history. But planned duplication requires updating all copies when the original changes — the application must handle this consistency manually.
Summary
MongoDB supports one-to-one, one-to-many, and many-to-many relationships through embedding and referencing. Embedding keeps related data in one document for fast reads. Referencing separates data into linked collections for large or shared datasets. The $lookup stage in aggregation pipelines performs joins between referenced collections. Self-referencing documents handle hierarchical relationships within a single collection. Choosing the right relationship model based on data access patterns is key to building efficient and maintainable MongoDB applications.
