Lesson 27 of 50 – Schema Design in MongoDB
54%

Schema Design in MongoDB

Schema design means planning how data will be organized and stored in MongoDB collections and documents. A good schema makes an application easier to maintain and can improve query performance.

1. What Is a Schema?

A schema describes the structure of data, including fields, data types, embedded documents, arrays, and relationships between records.

MongoDB is flexible-schema by default. Documents in the same collection can have different fields, although applications can enforce a consistent structure using schema validation.

// Student document 1
{
    name: "Rahul",
    age: 20,
    course: "Java"
}

// Student document 2
{
    name: "Priya",
    course: "Python",
    fees: 6000
}

Both documents can exist in the same collection even though they do not contain exactly the same fields.

2. Understand Your Application Requirements

Before creating collections, identify what information the application needs and how it will access that data.

For example, a student management system may need:

  • Student names, contact information, and admission dates.
  • Course details and course fees.
  • Fee payments and outstanding balances.
  • Attendance records.
  • Assignments and examination results.

Ask these questions before designing the schema:

  1. Which information is usually displayed together?
  2. How often does each piece of information change?
  3. Can a record have many related records?
  4. How large can an array or embedded document become?
  5. Which queries need to be fast?

3. Embedded Documents

An embedded document stores related information inside another document. This is useful when the related data is commonly read together and has a manageable size.

db.schema_students.insertOne({
    name: "Rahul",
    age: 20,
    address: {
        city: "Aurangabad",
        state: "Bihar",
        pinCode: "824101"
    }
})

Retrieve the student's city using dot notation:

db.schema_students.find(
    { name: "Rahul" },
    { _id: 0, name: 1, "address.city": 1 }
)

Advantages of embedding:

  • Related information can be retrieved in one query.
  • It can reduce the need for joins between collections.
  • Updates to one document can be atomic.

Avoid embedding unbounded arrays or very large related data that may cause documents to grow excessively.

4. Referenced Documents

Referencing stores related information in separate documents and connects them using an identifier. It is useful when related data is large, shared by many records, or managed independently.

// Students collection
{
    _id: 101,
    name: "Rahul",
    course_id: 1
}

// Courses collection
{
    _id: 1,
    course_name: "Java",
    fees: 5000
}

Find the student's course reference:

db.students.findOne({ _id: 101 })

To retrieve the related course details, an application can query the courses collection using course_id, or use an aggregation pipeline with $lookup.

db.students.aggregate([
    {
        $lookup: {
            from: "courses",
            localField: "course_id",
            foreignField: "_id",
            as: "courseDetails"
        }
    }
])

In this example, course_id must use a value compatible with the course document's _id. MongoDB does not automatically create a foreign-key constraint for this relationship.

5. Embedding vs Referencing

Feature Embedding Referencing
Data storage Inside one document In separate documents
Read related data Often available in one query May require additional queries or $lookup
Shared information May duplicate information Can store shared information once
Growing relationships May become unsuitable for unbounded data Often suitable for large or growing relationships
Best use Small, closely related data Large, shared, or independently managed data

Neither approach is always better. Choose based on application requirements, data growth, and query patterns.

6. One-to-One Relationship

A one-to-one relationship means one record is associated with one related record.

For example, a student may have one profile:

{
    student_id: 101,
    name: "Rahul",
    profile: {
        dateOfBirth: "2005-04-15",
        city: "Aurangabad"
    }
}

If the profile is small and usually accessed with the student, embedding can be a good choice. If it has different access permissions or is managed independently, a separate collection may be more appropriate.

7. One-to-Many Relationship

A one-to-many relationship means one record is associated with multiple related records.

Example: one student has several fee payments.

// students collection
{
    _id: 101,
    name: "Rahul"
}

// fee_payments collection
{
    student_id: 101,
    amount: 2000,
    payment_date: "2026-09-01"
}

{
    student_id: 101,
    amount: 1500,
    payment_date: "2026-10-01"
}

Keeping payments in a separate collection is often appropriate because payment records can grow over time and may need to be queried independently.

Find all payments for Rahul's student ID:

db.fee_payments.find({
    student_id: 101
})

An index on student_id can help queries that frequently retrieve payments for one student.

db.fee_payments.createIndex({
    student_id: 1
})

8. Many-to-Many Relationship

A many-to-many relationship means multiple records from one collection can be related to multiple records in another collection.

For example:

  • A student can enroll in several courses.
  • A course can have several students.

One option is to maintain enrollment documents in a separate collection.

// enrollments collection
{
    student_id: 101,
    course_id: 1,
    admission_date: "2026-09-01"
}

{
    student_id: 101,
    course_id: 2,
    admission_date: "2026-09-05"
}

{
    student_id: 102,
    course_id: 1,
    admission_date: "2026-09-03"
}

This design makes it possible to query enrollments by student or by course. Consider adding indexes on the fields commonly used for these queries.

db.enrollments.createIndex({ student_id: 1 })
db.enrollments.createIndex({ course_id: 1 })

9. Avoid Unbounded Arrays

An unbounded array is an array that can keep growing without a practical limit. Storing every payment, attendance entry, or message inside one document can make that document too large and difficult to manage.

Example of a potentially problematic design:

{
    name: "Rahul",
    allPayments: [
        { amount: 2000 },
        { amount: 1500 },
        { amount: 1000 }
        // This array may continue growing indefinitely.
    ]
}

A more scalable approach is to store payments in a separate collection linked by student_id.

{
    student_id: 101,
    amount: 2000,
    payment_date: "2026-09-01"
}

MongoDB documents have a maximum BSON document size of 16 MiB, so document growth should be considered when designing the schema.

10. Use Consistent Data Types

Store the same type of information using consistent data types. For example, course fees should generally be numeric rather than a mixture of numbers and strings.

// Recommended
{
    name: "Rahul",
    fees: 5000
}

// Avoid inconsistent types
{
    name: "Priya",
    fees: "6000"
}

In the second document, fees is a string. This can cause unexpected results in numeric calculations and comparisons.

MongoDB supports schema validation rules to help ensure that documents follow your application's requirements.

11. Schema Design Best Practices

  • Design around the queries your application runs most often.
  • Embed small related data that is frequently read together.
  • Reference large, shared, or independently managed data.
  • Avoid unbounded arrays inside a single document.
  • Use consistent field names and data types.
  • Create indexes for frequently queried fields when beneficial.
  • Use schema validation for important data rules.
  • Consider how documents will grow as the application develops.
  • Test query performance with realistic data volumes.

12. Practical Assignment

Design a MongoDB schema for a student management system.

  1. Create a student document with name, mobile number, and address.
  2. Embed the student's address inside the student document.
  3. Create a separate courses collection.
  4. Create a fee payments collection linked using student ID.
  5. Explain why fee payments may be better stored separately.
  6. Design an enrollments collection for students taking multiple courses.
  7. Choose appropriate indexes for searching payments and enrollments.
  8. Explain when embedding is preferable to referencing.

🧠 Quick Quiz

1. What does schema design mean?




2. When is referencing often a good choice?




3. What is an unbounded array?