DEV Community

Harsh Mishra
Harsh Mishra

Posted on

Understanding Relationships in MongoDB and Mongoose

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:

  1. Embedded Documents (Denormalization)
  2. 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"
  }
}
Enter fullscreen mode Exit fullscreen mode

Mongoose Schema

const userSchema = new mongoose.Schema({
  name: String,
  profile: {
    bio: String
  }
});
const User = mongoose.model("User", userSchema);
Enter fullscreen mode Exit fullscreen mode

Querying in Mongoose

const user = await User.findOne();
console.log(user);
Enter fullscreen mode Exit fullscreen mode

Example Output

{
  "_id": "user_id",
  "name": "John",
  "profile": {
    "bio": "Loves coding"
  }
}
Enter fullscreen mode Exit fullscreen mode

Method 2: Using References (Normalization)

MongoDB Schema (Separate Documents)

{
  "_id": ObjectId("user_id"),
  "name": "John",
  "profile": ObjectId("profile_id")
}
Enter fullscreen mode Exit fullscreen mode
{
  "_id": ObjectId("profile_id"),
  "bio": "Loves coding",
  "user": ObjectId("user_id")
}
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

Querying in Mongoose (Using .populate())

const user = await User.findOne().populate("profile");
console.log(user);
Enter fullscreen mode Exit fullscreen mode

Example Output

{
  "_id": "user_id",
  "name": "John",
  "profile": {
    "_id": "profile_id",
    "bio": "Loves coding"
  }
}
Enter fullscreen mode Exit fullscreen mode

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!" }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Mongoose Schema

const userSchema = new mongoose.Schema({
  name: String,
  posts: [
    {
      title: String,
      content: String
    }
  ]
});
const User = mongoose.model("User", userSchema);
Enter fullscreen mode Exit fullscreen mode

Querying in Mongoose

const user = await User.findOne();
console.log(user);
Enter fullscreen mode Exit fullscreen mode

Example Output

{
  "_id": "user_id",
  "name": "John",
  "posts": [
    { "title": "MongoDB Guide", "content": "MongoDB is great!" },
    { "title": "Mongoose Intro", "content": "Mongoose is helpful!" }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Method 2: Using References (Normalization)

MongoDB Schema

{
  "_id": ObjectId("user_id"),
  "name": "John",
  "posts": [ObjectId("post1_id"), ObjectId("post2_id")]
}
Enter fullscreen mode Exit fullscreen mode
{
  "_id": ObjectId("post1_id"),
  "title": "MongoDB Guide",
  "content": "MongoDB is great!",
  "user": ObjectId("user_id")
}
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

Querying in Mongoose

const user = await User.findOne().populate("posts");
console.log(user);
Enter fullscreen mode Exit fullscreen mode

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!" }
  ]
}
Enter fullscreen mode Exit fullscreen mode

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")]
}
Enter fullscreen mode Exit fullscreen mode
{
  "_id": ObjectId("course1_id"),
  "title": "Math 101",
  "students": [ObjectId("student_id")]
}
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

Querying in Mongoose

const student = await Student.findOne().populate("courses");
console.log(student);
Enter fullscreen mode Exit fullscreen mode

Example Output

{
  "name": "John",
  "courses": [
    { "_id": "course1_id", "title": "Math 101" },
    { "_id": "course2_id", "title": "Physics 201" }
  ]
}
Enter fullscreen mode Exit fullscreen mode

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)