MongoDB Schema Design Patterns: What I Learned Building Production Systems
MongoDB gives you schema flexibility but that does not mean schema-less. Here are the patterns I use in production to design MongoDB schemas that stay maintainable as requirements change.
MongoDB Schema Design Patterns: What I Learned Building Production Systems
When developers first encounter MongoDB, the schema flexibility feels liberating. No migrations, no ALTER TABLE, just insert documents and figure out the structure later. That freedom is real — and it is also a trap if you do not think carefully about how your data will be accessed.
Here are the patterns I use in production MongoDB deployments, drawn from Work Log Pro and other real systems.
1. Design for Query Patterns, Not Entities
The most important MongoDB design principle: know your access patterns before you design your schema.
In a relational database, you normalise your data into entities with foreign keys, and JOINs let you recombine them at query time. In MongoDB, JOINs ($lookup) are expensive — you want to structure your documents so common queries touch as few collections as possible.
- What are the most common read queries?
- What is the read/write ratio?
- What data is always accessed together?
If user profile data and user settings are always loaded together, put them in the same document. If they are occasionally loaded separately and frequently updated independently, separate them.
2. Embedding vs Referencing
The fundamental MongoDB schema decision is whether to embed related data in a document or reference it by ID.
- The child data is always loaded with the parent
- The child data has a bounded size (not an unbounded array)
- The relationship is one-to-one or one-to-few
- The child data is large or unbounded
- The child data is accessed independently of the parent
- The relationship is many-to-many
In Work Log Pro, I embed tags directly on work log entries (bounded, always loaded together) but reference projects by ID (projects are large documents accessed independently).
// Work log entry — tags embedded, project referenced
{
_id: ObjectId("..."),
userId: ObjectId("..."),
projectId: ObjectId("..."), // reference
description: "Built the auth module",
hours: 3.5,
date: ISODate("2026-08-13"),
tags: ["backend", "auth"] // embedded array, bounded
}