How to design an effective MongoDB document schema
Design a MongoDB schema from access patterns, choosing embedding for bounded co-accessed data and referencing for unbounded or shared data, then add indexes and JSON Schema validation.
What and why
MongoDB has no fixed schema, so the model is driven by how the application reads and writes data, not by normalization rules. Good design embeds data that is read together and references data that grows unbounded or is shared. This tutorial walks through the key decisions and how to enforce them.
Prerequisites
- A MongoDB instance reachable via
mongoshor a driver. - A clear picture of your most frequent queries.
- Basic knowledge of BSON documents.
Steps
1. Map your access patterns
List the top read and write operations and how often each runs. Design for the queries you make most, since MongoDB rewards data locality.
2. Decide embed vs reference
Embed when data is accessed together, has a bounded size, and changes with the parent. Reference when sub-documents grow without limit, are large, or are shared across parents. Watch the 16 MB document limit.
3. Model one-to-many relationships
For a small, bounded list (an order's line items), embed:
{
_id: ObjectId(),
customer: "Ada",
items: [ { sku: "A1", qty: 2 }, { sku: "B7", qty: 1 } ]
}
For an unbounded list (a user's events), store events in their own collection with a userId field.
4. Handle many-to-many
Keep two collections and store an array of references on the side queried most. For products and tags, an array of tag ids on the product is usually enough:
{ _id: ObjectId(), name: "Widget", tagIds: [ObjectId(), ObjectId()] }
5. Add indexes
Index the fields used in filters and sorts:
db.events.createIndex({ userId: 1, createdAt: -1 });
Use compound indexes ordered to match equality-then-range queries.
6. Enforce schema validation
Add a JSON Schema validator so documents stay consistent:
db.createCollection("users", {
validator: { $jsonSchema: {
bsonType: "object",
required: ["email"],
properties: { email: { bsonType: "string" } }
} }
});
Verification
Run your top queries with .explain("executionStats") and confirm they use indexes (IXSCAN, not COLLSCAN). Insert an invalid document and confirm validation rejects it.
Next Steps
Review the schema as access patterns change, use the bucket pattern for high-volume time-series data, and add a shard key aligned to your query distribution before scaling out.
Prerequisites
- A running MongoDB instance
- Basic JSON and query knowledge
- Understanding of your read/write patterns
Steps
- 1Map your access patterns
- 2Decide embed vs reference
- 3Model one-to-many relationships
- 4Handle many-to-many
- 5Add indexes
- 6Enforce schema validation