Data Modeling & Relationships
MongoDB does not have foreign keys or JOIN tables like SQL. Instead you model relationships by embedding documents inside a parent or storing references (ObjectIds) to other collections. The right choice depends on how you read and write data.
Relationships in MongoDB
MongoDB does not have foreign keys or JOIN tables like SQL. Instead you model relationships by embedding documents inside a parent or storing references (ObjectIds) to other collections. The right choice depends on how you read and write data.
Real-life example: A school report card can list subjects and grades on one sheet (embedding) or refer to a separate marks register by student ID (reference). Teachers reading one student's full report prefer the sheet; auditors updating one subject across 500 students prefer the register.
- One-to-one — embed if always accessed together (user + profile settings); reference if large or shared.
- One-to-many — embed for small, bounded lists (order line items); reference for unbounded (user's orders).
- Many-to-many — use array of ObjectIds on one side, or a junction collection for complex cases.
- Always ask: What does my most common query look like? Design the document shape to answer it in one read.
Embed vs reference — decision guide
Embed when data is one-to-few, read together with the parent, and does not grow without limit. Reference when data is large, updated independently, shared across many parents, or queried on its own.
The hybrid pattern embeds a snapshot (name, thumbnail) and keeps the full record in another collection — great for product catalogs where orders need a price snapshot at purchase time.
Real-life example: A pizza order embeds toppings (few items, never queried alone). The customer profile lives in users collection (referenced by userId) because the same person places many orders.
// orders collection — items embedded (one-to-few, read with order)
{
_id: ObjectId("..."),
userId: ObjectId("665a1b2c3d4e5f6789012345"),
status: "completed",
items: [
{ productId: ObjectId("..."), name: "Wireless Mouse", qty: 2, price: 899 },
{ productId: ObjectId("..."), name: "USB Hub", qty: 1, price: 1299 }
],
total: 3097,
createdAt: ISODate("2024-06-15T10:30:00Z")
}// students collection
{ _id: ObjectId("s1"), name: "Asha", enrolledCourseIds: [ObjectId("c1"), ObjectId("c2")] }
// courses collection
{ _id: ObjectId("c1"), title: "MongoDB Fundamentals", studentCount: 142 }
// Or separate enrollments collection for rich metadata
{ studentId: ObjectId("s1"), courseId: ObjectId("c1"), enrolledAt: ISODate("..."), grade: "A" }Unbounded arrays — the silent killer
Never embed an array that grows forever. Comments on a viral post, log entries, sensor readings, or notification lists will eventually hit the 16 MB document size limit and slow every read/write on that document.
Move unbounded data to a separate collection with a foreign key (postId, userId). Paginate with find().sort().limit(). Index the foreign key field.
Real-life example: A WhatsApp group chat with millions of messages does not store all messages inside the group document — each message is its own record linked by groupId.
// BAD — unbounded array inside post document
{
_id: ObjectId("post1"),
title: "MongoDB Tips",
comments: [ /* grows forever — document bloat */ ]
}
// GOOD — separate comments collection
// posts: { _id, title, commentCount: 4821 }
// comments: { postId, authorId, text, createdAt }
db.comments.createIndex({ postId: 1, createdAt: -1 })
db.comments.find({ postId: ObjectId("post1") }).sort({ createdAt: -1 }).limit(20)Rishtaara modeling checklist
- List your top 5 queries — design documents to satisfy them in one find() or a simple aggregation.
- Identify one-to-few vs one-to-many vs many-to-many for each relationship.
- Embed snapshots at write time for data that can change later (product price, user display name).
- Index reference fields (userId, postId) on child collections.
- Run through growth scenarios — what happens at 1M users, 10M orders, 100M events?