Skip to main content

Mongoose Model Relationship

· 4 min read
Sivabharathy

Mongoose provides a powerful way to model relationships between documents in your MongoDB collections. Here's an explanation of Mongoose populate with examples for one-to-one, one-to-many, and many-to-many relationships:

1. One-to-One Relationship:

This represents a scenario where a document in one collection can have a reference to at most one document in another collection. Here's an example:

  • User Schema:
const mongoose = require('mongoose');

const userSchema = new mongoose.Schema({
name: String,
profile: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Profile' // Reference to the Profile model
}
});

module.exports = mongoose.model('User', userSchema);
  • Profile Schema:
const profileSchema = new mongoose.Schema({
bio: String,
user: { // This can also be an ObjectId, but referencing the User model provides user details directly
type: mongoose.Schema.Types.ObjectId,
ref: 'User' // Reference back to the User model (optional)
}
});

module.exports = mongoose.model('Profile', profileSchema);

Explanation:

  • The User schema has a field called profile of type ObjectId. This references the _id field of another collection called Profile.
  • The Profile schema can also have a field called user that references the User model (optional).

Populating User with Profile:

User.findById(userId)
.populate('profile') // Populate the 'profile' field with the corresponding Profile document
.exec((err, user) => {
if (err) {
console.error(err);
return;
}
console.log(user); // user object will now include the populated profile data
});

2. One-to-Many Relationship:

This represents a scenario where a document in one collection can have references to multiple documents in another collection. Here's an example:

  • Author Schema:
const authorSchema = new mongoose.Schema({
name: String,
books: [{ // Array of ObjectIds referencing the Book model
type: mongoose.Schema.Types.ObjectId,
ref: 'Book'
}]
});

module.exports = mongoose.model('Author', authorSchema);
  • Book Schema:
const bookSchema = new mongoose.Schema({
title: String,
author: { // ObjectId referencing the Author model
type: mongoose.Schema.Types.ObjectId,
ref: 'Author'
}
});

module.exports = mongoose.model('Book', bookSchema);

Explanation:

  • The Author schema has a field called books which is an array of ObjectIds referencing documents in the Book collection.
  • The Book schema has a field called author referencing the Author model.

Populating Author with Books:

Author.findById(authorId)
.populate('books') // Populate the 'books' field with the corresponding Book documents
.exec((err, author) => {
if (err) {
console.error(err);
return;
}
console.log(author); // author object will include an array of populated Book documents
});

3. Many-to-Many Relationship:

Mongoose doesn't directly support referencing documents in a many-to-many relationship within the schema definition itself. However, there are two common approaches to model many-to-many relationships in Mongoose:

  1. Reference Model (Normalization): This approach involves creating a separate model to represent the intersection table between the two collections.
  • Example:

    • Course Schema:

      const courseSchema = new mongoose.Schema({
      name: String
      });

      module.exports = mongoose.model('Course', courseSchema);
    • Student Schema:

      const studentSchema = new mongoose.Schema({
      name: String
      });

      module.exports = mongoose.model('Student', studentSchema);
    • CourseStudent Schema (Intersection table):

      const courseStudentSchema = new mongoose.Schema({
      courseId: {
      type: mongoose.Schema.Types.ObjectId,
      ref: 'Course'
      },
      studentId: {
      type: mongoose.Schema.Types.ObjectId,
      ref: 'Student'
      }
      });

      module.exports = mongoose.model('CourseStudent', courseStudentSchema);

    Explanation:

    • A separate CourseStudent model is created with two fields: courseId and studentId.
    • Both fields reference their respective models (Course and Student).
    • A Course document can have many CourseStudent documents referencing it, and a Student document can also have many CourseStudent documents.
  1. Embedded Documents (Denormalization): This approach involves embedding an array of references directly within each model's schema.
  • Example:

    • Course Schema:

      const courseSchema = new mongoose.Schema({
      name: String,
      students: [{
      type: mongoose.Schema.Types.ObjectId,
      ref: 'Student'
      }]
      });

      module.exports = mongoose.model('Course', courseSchema);
    • Student Schema:

      const studentSchema = new mongoose.Schema({
      name: String,
      courses: [{
      type: mongoose.Schema.Types.ObjectId,
      ref: 'Course'
      }]
      });

      module.exports = mongoose.model('Student', studentSchema);

    Explanation:

    • Both Course and Student schemas have a field called students which is an array of ObjectIds referencing documents in the other collection.

Choosing the Right Approach:

  • Normalization (Reference Model):
    • This approach is preferred when data integrity is a high concern and you need to independently modify courses or students without affecting the other.
    • It avoids data duplication but requires additional queries to fetch all related data.
  • Denormalization (Embedded Documents):
    • This approach improves query performance as related data can be retrieved in a single query.
    • However, it can lead to data duplication if a student enrolls in multiple courses (course name will be stored in each student document) and requires careful handling of updates to avoid inconsistencies.

Populating Data with Many-to-Many Relationships:

Use similar techniques as one-to-many relationships to populate data with the chosen approach (referencing CourseStudent documents or the students array).