MongoDBData Modeling

Data Modeling

MongoDB is a document database, and how you shape your documents matters far more than it does in SQL. The golden rule: model your data around your application's access patterns, not around normalization. A single document read is cheaper than a multi-collection join, so co-locate data that is always read together and separate data that is queried independently.

Embedding vs Referencing

Every modeling decision comes down to one question: should this data live inside the parent document (embed), or in its own collection with a reference (like a foreign key)?

Factor

Embed

Reference

Accessed together?

Yes — always read together

No — queried independently

Changes together?

Yes — updated atomically

No — updated separately

Data size

Small (document stays <16 MB)

Large or unbounded

1:1 relationship

Embed

Either

1:few relationship

Embed

Either

1:many relationship

Either

Reference

Many:many relationship

Rarely

Reference

One-to-One: Embed It

When two entities always appear together and neither grows unboundedly, embed one inside the other.

JS
// ✅ Embedded: user + profile in one document
{
  _id: ObjectId("..."),
  name: "Alice",
  email: "alice@example.com",
  profile: {
    bio: "Software engineer",
    avatarUrl: "https://cdn.example.com/alice.jpg",
    location: "Berlin"
  }
}

// ❌ Separate collections — forces a second query every time
// users collection:    { _id, name, email }
// profiles collection: { userId, bio, avatarUrl, location }
One-to-Few: Embed It
Note
"Few" means roughly 2–15 items that are bounded and always read with the parent. Order line items, user addresses, and phone numbers are all classic 1:few relationships.

JS
// ✅ Order with embedded line items (bounded array)
{
  _id: ObjectId("..."),
  orderNumber: "ORD-1042",
  customerId: ObjectId("..."),
  placedAt: ISODate("2024-03-15T10:00:00Z"),
  lineItems: [
    { productId: ObjectId("..."), name: "Keyboard", qty: 1, unitPrice: 89.99 },
    { productId: ObjectId("..."), name: "Mouse",    qty: 2, unitPrice: 29.99 },
    { productId: ObjectId("..."), name: "USB Hub",  qty: 1, unitPrice: 19.99 }
  ],
  total: 169.96
}
One-to-Many: Reference It

When the "many" side can grow without bound (e.g. comments on a post), store references instead of embedding — otherwise the parent document will balloon past the 16 MB limit.

JS
// posts collection
{
  _id: ObjectId("post1"),
  title: "Getting Started with MongoDB",
  body: "...",
  authorId: ObjectId("user1"),
  commentIds: [
    ObjectId("comment1"),
    ObjectId("comment2"),
    ObjectId("comment3")
  ]
}

// comments collection (each comment references its post)
{
  _id: ObjectId("comment1"),
  postId: ObjectId("post1"),
  authorId: ObjectId("user2"),
  text: "Great article!",
  createdAt: ISODate("2024-03-16T09:00:00Z")
}

// Fetch post then its comments
const post = await db.collection('posts').findOne({ _id: postId })
const comments = await db.collection('comments')
  .find({ _id: { $in: post.commentIds } })
  .toArray()
Many-to-Many: Reference It

Store an array of references on both sides of the relationship. Neither collection embeds the other.

JS
// students collection
{
  _id: ObjectId("student1"),
  name: "Bob",
  courseIds: [ObjectId("course1"), ObjectId("course2")]
}

// courses collection
{
  _id: ObjectId("course1"),
  title: "Introduction to Databases",
  studentIds: [ObjectId("student1"), ObjectId("student3"), ObjectId("student7")]
}

// Find all courses for a student
const student = await db.collection('students').findOne({ _id: studentId })
const courses = await db.collection('courses')
  .find({ _id: { $in: student.courseIds } })
  .toArray()
The Subset Pattern

When you only ever display the most recent N items (e.g. the last 5 reviews on a product page), keep only that subset embedded in the parent document. Store the full list in a separate collection. This avoids loading thousands of subdocuments for a summary widget.

JS
// products collection — subset of reviews embedded
{
  _id: ObjectId("prod1"),
  name: "Mechanical Keyboard",
  price: 129.99,
  reviewCount: 342,
  avgRating: 4.7,
  recentReviews: [
    { userId: ObjectId("..."), rating: 5, text: "Love it!",      date: ISODate("2024-03-14") },
    { userId: ObjectId("..."), rating: 4, text: "Solid build.",  date: ISODate("2024-03-13") },
    { userId: ObjectId("..."), rating: 5, text: "Fast shipping.",date: ISODate("2024-03-12") },
    { userId: ObjectId("..."), rating: 3, text: "A bit loud.",   date: ISODate("2024-03-11") },
    { userId: ObjectId("..."), rating: 5, text: "Perfect.",      date: ISODate("2024-03-10") }
  ]
}

// reviews collection — full history
{
  _id: ObjectId("rev1"),
  productId: ObjectId("prod1"),
  userId: ObjectId("..."),
  rating: 5,
  text: "Love it!",
  date: ISODate("2024-03-14")
}
Document Size Limit
Warning
MongoDB enforces a hard 16 MB limit per document. Arrays that grow without bound (e.g. appending every event, log entry, or user action into one document) will eventually hit this ceiling and cause write errors. If an array can grow to hundreds or thousands of items, use referencing or the Bucket Pattern instead.
Schema Validation

MongoDB is schemaless by default, but you can enforce a schema at the collection level using JSON Schema validation. Invalid documents are rejected at write time.

JS
db.createCollection("users", {
  validator: {
    $jsonSchema: {
      bsonType: "object",
      required: ["name", "age", "email"],
      properties: {
        name: {
          bsonType: "string",
          description: "must be a string and is required"
        },
        age: {
          bsonType: "int",
          minimum: 0,
          maximum: 120,
          description: "must be an integer between 0 and 120"
        },
        email: {
          bsonType: "string",
          description: "must be a valid email address"
        }
      }
    }
  },
  validationAction: "error"
})
Tip
Embed when the data is always needed together and the size stays bounded. Reference when the data is needed independently, can grow without limit, or is shared across many parent documents.