Understanding Relationships in MongoDB & Mongoose
Introduction
MongoDB, being a NoSQL database, handles relationships differently than relational databases like MySQL. Instead of foreign keys, it uses Embedded Documents (Denormalization) and References (Normalization) to establish connections between data.
In this guide, we’ll explore:
- One-to-One (1:1) Relationships
- One-to-Many (1:M) Relationships
- Many-to-Many (M:N) Relationships
- How to define them in MongoDB and Mongoose
- How to query them and what the results look like
Let’s dive in! 🚀
1. Types of Relationships in MongoDB
In MongoDB, relationships can be represented in two ways:
- Embedded Documents (Denormalization)
- References (Normalization)
Just like MySQL, MongoDB supports:
- One-to-One (1:1)
- One-to-Many (1:M)
- Many-to-Many (M:N)
For each type, I’ll cover:
- MongoDB Schema (Embedded & Referenced)
- Querying in MongoDB
- How to Define it in Mongoose
- Querying with Mongoose
- Example Output
2. One-to-One (1:1) Relationship
Example Scenario
A User has one Profile, and a Profile belongs to only one User.
Method 1: Using Embedded Document (Denormalization)
MongoDB Schema
{
"_id": ObjectId("user_id"),
"name": "John",
"profile": {
"bio": "Loves coding"
}
}
Mongoose Schema
const userSchema = new mongoose.Schema({
name: String,
profile: {
bio: String
}
});
const User = mongoose.model("User", userSchema);
Querying in Mongoose
const user = await User.findOne();
console.log(user);
Example Output
{
"_id": "user_id",
"name": "John",
"profile": {
"bio": "Loves coding"
}
}
Method 2: Using References (Normalization)
MongoDB Schema (Separate Documents)
{
"_id": ObjectId("user_id"),
"name": "John",
"profile": ObjectId("profile_id")
}
{
"_id": ObjectId("profile_id"),
"bio": "Loves coding",
"user": ObjectId("user_id")
}
Mongoose Schema
const profileSchema = new mongoose.Schema({
bio: String,
user: { type: mongoose.Schema.Types.ObjectId, ref: "User" }
});
const userSchema = new mongoose.Schema({
name: String,
profile: { type: mongoose.Schema.Types.ObjectId, ref: "Profile" }
});
const Profile = mongoose.model("Profile", profileSchema);
const User = mongoose.model("User", userSchema);
Querying in Mongoose (Using .populate()
)
const user = await User.findOne().populate("profile");
console.log(user);
Example Output
{
"_id": "user_id",
"name": "John",
"profile": {
"_id": "profile_id",
"bio": "Loves coding"
}
}
3. One-to-Many (1:M) Relationship
Example Scenario
A User has many Posts, but each Post belongs to only one User.
Method 1: Using Embedded Documents (Denormalization)
MongoDB Schema
{
"_id": ObjectId("user_id"),
"name": "John",
"posts": [
{ "title": "MongoDB Guide", "content": "MongoDB is great!" },
{ "title": "Mongoose Intro", "content": "Mongoose is helpful!" }
]
}
Mongoose Schema
const userSchema = new mongoose.Schema({
name: String,
posts: [
{
title: String,
content: String
}
]
});
const User = mongoose.model("User", userSchema);
Querying in Mongoose
const user = await User.findOne();
console.log(user);
Example Output
{
"_id": "user_id",
"name": "John",
"posts": [
{ "title": "MongoDB Guide", "content": "MongoDB is great!" },
{ "title": "Mongoose Intro", "content": "Mongoose is helpful!" }
]
}
Method 2: Using References (Normalization)
MongoDB Schema
{
"_id": ObjectId("user_id"),
"name": "John",
"posts": [ObjectId("post1_id"), ObjectId("post2_id")]
}
{
"_id": ObjectId("post1_id"),
"title": "MongoDB Guide",
"content": "MongoDB is great!",
"user": ObjectId("user_id")
}
Mongoose Schema
const postSchema = new mongoose.Schema({
title: String,
content: String,
user: { type: mongoose.Schema.Types.ObjectId, ref: "User" }
});
const userSchema = new mongoose.Schema({
name: String,
posts: [{ type: mongoose.Schema.Types.ObjectId, ref: "Post" }]
});
const Post = mongoose.model("Post", postSchema);
const User = mongoose.model("User", userSchema);
Querying in Mongoose
const user = await User.findOne().populate("posts");
console.log(user);
Example Output
{
"_id": "user_id",
"name": "John",
"posts": [
{ "_id": "post1_id", "title": "MongoDB Guide", "content": "MongoDB is great!" },
{ "_id": "post2_id", "title": "Mongoose Intro", "content": "Mongoose is helpful!" }
]
}
4. Many-to-Many (M:N) Relationship
Example Scenario
A Student can enroll in many Courses, and a Course can have many Students.
Using References with a Junction Collection
MongoDB Schema
{
"_id": ObjectId("student_id"),
"name": "John",
"courses": [ObjectId("course1_id"), ObjectId("course2_id")]
}
{
"_id": ObjectId("course1_id"),
"title": "Math 101",
"students": [ObjectId("student_id")]
}
Mongoose Schema
const studentSchema = new mongoose.Schema({
name: String,
courses: [{ type: mongoose.Schema.Types.ObjectId, ref: "Course" }]
});
const courseSchema = new mongoose.Schema({
title: String,
students: [{ type: mongoose.Schema.Types.ObjectId, ref: "Student" }]
});
const Student = mongoose.model("Student", studentSchema);
const Course = mongoose.model("Course", courseSchema);
Querying in Mongoose
const student = await Student.findOne().populate("courses");
console.log(student);
Example Output
{
"name": "John",
"courses": [
{ "_id": "course1_id", "title": "Math 101" },
{ "_id": "course2_id", "title": "Physics 201" }
]
}
5. Summary Table
Relationship | Embedded Documents | References with .populate()
|
---|---|---|
One-to-One | { profile: { bio: "text" } } |
{ profile: ObjectId("profile_id") } |
One-to-Many | { posts: [ {title, content} ] } |
{ posts: [ObjectId("post_id")] } |
Many-to-Many | ❌ (not ideal) | { courses: [ObjectId("course_id")] } |
Conclusion
- Embedded documents are good for fast reads but increase duplication.
-
References with
.populate()
keep data normalized but require joins. -
Mongoose makes relationships easy using
Schema.Types.ObjectId
.
Top comments (0)