Document stores hold flexible, nested JSON-like records and let you query any field. Learn MongoDB — documents and collections, querying, indexes, and the embed-vs-reference modeling decision.
A document database stores records as self-contained JSON-like documents (BSON in MongoDB) grouped into collections. Unlike a key-value store, you can query on any field inside the document, and unlike a relational row, a document can nest arrays and sub-objects. This suits data that is naturally hierarchical and whose shape varies or evolves — profiles, catalogs, content.
// a MongoDB document — nested, self-contained, no fixed schema
db.users.insertOne({
_id: 1001,
name: "Ann",
email: "ann@x.com",
addresses: [ // arrays + nested objects live inline
{ type: "home", city: "Austin" },
{ type: "work", city: "Dallas" }
],
plan: "pro"
})
// documents in one collection can differ in shape (flexible schema).
// Great for data that's hierarchical and evolving.Document stores let you query by any field, including inside nested arrays, and run aggregation pipelines for grouping and transformation. As with any database, queries are only fast with indexes: without one, the engine scans every document. Indexing the fields you filter and sort on is the single most important performance step, exactly as in a relational database.
// query by any field, including nested ones
db.users.find({ plan: "pro", "addresses.city": "Austin" })
// aggregation pipeline: group + summarize (like GROUP BY)
db.orders.aggregate([
{ $match: { status: "completed" } },
{ $group: { _id: "$customer_id", total: { $sum: "$amount" } } },
{ $sort: { total: -1 } }
])
// indexes make it fast — without one, this SCANS every document:
db.users.createIndex({ plan: 1, "addresses.city": 1 })The defining modeling choice in a document database is whether to embed related data inside a document or reference it in a separate collection. Embed when data is read together and owned by the parent (order line items); reference when data is large, shared, or changes independently (a product referenced by many orders). This is the document-world version of denormalize-for-reads.
EMBED (nest inside the document):
order { id, items: [ {sku, qty, price}, ... ] }
✓ read the whole order in ONE query, no join
✗ duplicated if shared; document can grow unbounded
use when: data is read together + owned by the parent + bounded in size
REFERENCE (store an id, look up separately):
order { id, customer_id: 1001 } + users { _id: 1001, ... }
✓ no duplication; shared data changes in one place
✗ needs a second query (or $lookup) to resolve
use when: data is large, shared, or changes independently
Rule of thumb: "read together -> embed; shared/large/independent -> reference."Document databases give developer velocity — the object in your code maps directly to a stored document — and scale horizontally by sharding on a key. Their limits are the flip side: multi-document joins are awkward and historically transactions across documents were unavailable (modern MongoDB supports them, but at a cost). Match them to workloads that read and write whole documents, not to heavily relational, multi-entity transactions.
Great at:
- object-shaped data (your code object == the stored document)
- flexible/evolving schemas, content, catalogs, profiles
- horizontal scale via sharding on a shard key
Weaker at:
- many-way joins across collections ($lookup exists but is limited)
- complex multi-document transactions (supported now, but has a cost)
- heavily relational data with lots of cross-entity constraints
Fit: workloads that read/write whole documents. For deeply relational
data, a relational DB or a graph DB is the better tool.