DEV Community

Harsh Mishra
Harsh Mishra

Posted on

1

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.

Hostinger image

Get n8n VPS hosting 3x cheaper than a cloud solution

Get fast, easy, secure n8n VPS hosting from $4.99/mo at Hostinger. Automate any workflow using a pre-installed n8n application and no-code customization.

Start now

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Engage with a wealth of insights in this thoughtful article, valued within the supportive DEV Community. Coders of every background are welcome to join in and add to our collective wisdom.

A sincere "thank you" often brightens someone’s day. Share your gratitude in the comments below!

On DEV, the act of sharing knowledge eases our journey and fortifies our community ties. Found value in this? A quick thank you to the author can make a significant impact.

Okay