MongoDB will happily store whatever you throw at it, which means the responsibility for good structure moves from the database to you. The difference between a fast, maintainable MongoDB application and a slow, tangled one is almost always schema design. This CodeYourCraft lesson teaches the design mindset, the workload-first process, and the tools MongoDB gives you to keep data honest.
Relational modeling starts from entities and normalizes them into tables. Document modeling starts from a different question: what does the application actually read and write, and how often? The guiding principle is that data that is accessed together should be stored together. If your product page always shows a product with its specs and top reviews, storing those in one document turns the hottest read in your app into a single document fetch.
Begin every design by listing your workload: the main screens and jobs, the queries behind them, their frequency, and whether they are read-heavy or write-heavy. Then shape documents so the most frequent operations touch the fewest documents.
Consider posts, authors, comments, and tags. A workload-first design might look like this:
// posts collection: one document renders the whole post page
{
_id: ObjectId('...'),
title: 'Designing MongoDB Schemas',
slug: 'designing-mongodb-schemas',
author: { _id: ObjectId('...'), name: 'Sara Iyer' }, // denormalized subset
tags: ['mongodb', 'database-design'],
body: '...',
commentCount: 128,
recentComments: [
{ user: 'dev_amit', text: 'Great write-up!', at: ISODate('...') }
]
}The author name is duplicated from the authors collection, a deliberate denormalization: post pages render without a join, and if the author renames, one updateMany fixes all their posts. Only recent comments are embedded; the full archive lives in a comments collection, keeping the post document small and bounded.
Duplicating data optimizes reads at the cost of coordinated writes. Duplicate fields that are read constantly but change rarely, such as names, titles, and thumbnails. Avoid duplicating anything that changes often or must always be exactly consistent, such as account balances. When you do duplicate, write down where each copy lives so update code can keep them in sync.
Flexibility should be a choice, not an accident. JSON Schema validation rejects malformed writes at the database boundary:
db.createCollection('customers', {
validator: {
$jsonSchema: {
bsonType: 'object',
required: ['name', 'email'],
properties: {
name: { bsonType: 'string' },
email: { bsonType: 'string', pattern: '^.+@.+$' },
points: { bsonType: 'int', minimum: 0 }
}
}
},
validationAction: 'error'
})Use validation for the invariants that must never break, while letting optional fields evolve freely.
The biggest single design decision, whether to embed related data or reference it, deserves its own lesson, and that is exactly what comes next.