How I Think About Database Relationships When Designing a MongoDB Schema
Before I write a single line of code, I sit down and figure out one thing — how my collections talk to each other. I got this wrong early on, and it caused me real headaches later. So let me show you the exact process I follow now.
The Three Questions I Ask Every Time
When I have two collections and I want to know their relationship, I ask three simple questions:
Can one A have many B's?
Can one B belong to many A's?
Who owns or creates this entity?
That's it. Everything else falls out of these.
The Four Relationship Types
Before I walk through examples, here's what each type actually means.
One-to-One — one user has one profile. Neither side has multiples.
One-to-Many — one user uploads many videos. The video stores the user's ID.
Many-to-One — same as above, just from the other direction. Many videos point to one user.
Many-to-Many — one video can be in many playlists, one playlist can have many videos. You need either an array of IDs or a separate junction collection.
How I Apply This to a Real Schema
Let me walk through the collections I was building for my YouTube-like platform.
Users → Videos
Can one user have many videos? Yes. Can one video belong to many users? No — a video has one creator.
That's One-to-Many. So the video stores the owner's ID:
videos {
owner: ObjectId // references users
}
The user collection stores nothing about videos. I query from the video side.
Playlists → Videos
Can one playlist have many videos? Yes. Can one video be in many playlists? Also yes.
That's Many-to-Many. I store an array of video IDs inside the playlist:
playlists {
videos: [ObjectId, ObjectId, ...] // references videos
}
Notice — the video doesn't need to know which playlists it belongs to. The playlist is the container, so it holds the reference. I don't store both sides. Storing both sides creates synchronization problems.
Users → Plan
Can one user have many plans? No — a user has one active plan. Can one plan belong to many users? Yes — many users can be on the "Premium" plan.
That's Many-to-One. So the user stores the plan ID:
users {
plan: ObjectId // references plans
}
Users ↔ Users (Subscriptions)
This one is interesting. A user can subscribe to many channels. A channel can have many subscribers. Both sides are users.
That's Many-to-Many, but it's self-referencing — both sides point to the same collection. I cover the exact collection design for this in my next article because there's a specific mistake worth talking about separately.
Who Stores the Reference?
Once I know the type, I ask: which side stores the ID?
The rule I follow — the "many" side stores the reference.
| Relationship | Who Stores the ID |
|---|---|
| One-to-Many | The child (many side) |
| Many-to-One | The many side |
| Many-to-Many | The container, or a junction collection |
| One-to-One | Whichever side you query from most |
The Full Decision Process
Can one A have many B's?
├── No → One-to-One (or Many-to-One from B's view)
└── Yes →
Can one B have many A's?
├── No → One-to-Many
└── Yes → Many-to-Many
Simple. I run every field through this before I write a single schema.
One More Thing About MongoDB Specifically
In SQL, you always have a separate join table for Many-to-Many. In MongoDB, you have two options:
Array of ObjectIds — works fine when the array is bounded (like playlist → videos)
Separate junction collection — better when the relationship itself has metadata, or when either side can grow very large
I use arrays for bounded cases and junction collections for everything else. Subscriptions, likes, watch history — those go into separate collections. I'll show why in the next article.
That's the whole framework. Ask the three questions, find the type, decide who stores the reference. Once this clicks, schema design gets a lot less confusing.


