Documents & Collections
MongoDB stores data as documents grouped into collections. A document is a single record — the rough equivalent of a row in a relational table — encoded as BSON (Binary JSON). A collection is a named group of documents — the rough equivalent of a table — except a collection places no fixed schema on the documents it holds.
Understanding the document/collection relationship is the foundation for everything else in MongoDB: querying, indexing, and data modeling all build on top of it.
What a Document Looks Like
A document is an ordered set of key-value pairs, written as BSON on disk and rendered as JSON-like syntax in mongosh. Keys are strings; values can be any BSON type, including other documents and arrays.
A single document
{
_id: ObjectId("64f1a2b3c4d5e6f7a8b9c0d1"),
name: "Alice Chen",
email: "alice@example.com",
age: 29,
active: true,
tags: ["admin", "beta-tester"],
address: {
city: "Toronto",
country: "CA"
},
createdAt: ISODate("2024-01-15T10:30:00Z")
}Every document has exactly one _id field, which acts as its primary key within the collection. If you don't supply one on insert, MongoDB generates an ObjectId automatically.
The _id Field
_idmust be unique within a collection — MongoDB enforces this with an automatic index on_idcreated the moment the collection exists.It is immutable — once a document is inserted, you cannot change its
_idwith an update; you would have to delete and re-insert.It can be any BSON type — an
ObjectId(the default), a string, a number, or even a compound (embedded document) key.It is always the first field returned unless explicitly excluded in a projection.
Custom _id values
// Natural key instead of an auto-generated ObjectId
db.currencies.insertOne({ _id: "USD", name: "US Dollar", symbol: "$" })
// Compound _id built from multiple business fields
db.inventory.insertOne({
_id: { warehouse: "YYZ", sku: "WIDGET-42" },
qty: 500
})Field Types Are Flexible
Unlike a relational table, a collection has no built-in requirement that every document share the same fields or field types. Two documents in the same collection can legally look completely different.
Heterogeneous documents in one collection
db.products.insertMany([
{ name: "Widget", price: 9.99, stock: 100 },
{ name: "Gadget", price: "24.50 CAD", onSale: true }, // price is a string here
{ name: "Gizmo", variants: [{ color: "red" }, { color: "blue" }] }
])$jsonSchema validator on the collection to reject documents that drift too far from the expected structure.Nested Documents and Arrays
The ability to embed documents and arrays inside a document is what lets MongoDB model rich, hierarchical data without joins. This is the biggest structural difference from a normalized relational schema.
Embedding related data
{
_id: ObjectId("64f1a2b3c4d5e6f7a8b9c0d2"),
orderNumber: "ORD-1042",
customer: { // embedded (sub-)document
name: "Bob Ivanov",
email: "bob@example.com"
},
items: [ // array of embedded documents
{ sku: "WIDGET-42", qty: 2, price: 9.99 },
{ sku: "GADGET-7", qty: 1, price: 24.50 }
],
tags: ["priority", "gift-wrap"], // array of scalars
total: 44.48
}Pattern | When to Use | Tradeoff |
|---|---|---|
Embed (nest) | Data is read together and belongs to a "contains" relationship (order + its line items). | Duplicated data across documents if the embedded value is shared; document can grow large. |
Reference (store an _id) | Data is large, shared across many parents, or updated independently (a product catalog referenced from many orders). | Requires a |
The 16 MB Document Size Limit
Every BSON document is capped at 16 MB. This limit exists so a single document can always fit comfortably in RAM and be transferred efficiently over the wire — MongoDB is not designed for documents that grow unbounded.
Checking a document's BSON size
const doc = db.orders.findOne({ orderNumber: "ORD-1042" })
Object.bsonsize(doc) // returns the size in bytes_id.What a Collection Is
A collection is created implicitly the first time you insert a document into it (or explicitly with db.createCollection() when you need options like schema validation, capped size, or a specific collation up front).
Creating and inspecting collections
// Implicit creation — the collection appears on first insert
db.reviews.insertOne({ productId: "WIDGET-42", rating: 5 })
// Explicit creation with options
db.createCollection("auditLog", {
capped: true,
size: 5 * 1024 * 1024, // 5 MB max size
max: 10000 // and/or a max document count
})
db.getCollectionNames()
db.reviews.drop() // removes the collection and its indexes entirelyThinking About Collection Design
Group by access pattern, not by "entity." Ask "what data do I read together, most often?" before asking "what are my nouns?"
One collection per bounded, independently-queried entity type —
users,orders,products— rather than one giant collection holding everything.Avoid unbounded growth inside a single document. Time-series events, comments, or logs that grow forever belong in their own collection, often with the parent id as a foreign key.
Favor duplication over indirection when reads dominate writes. MongoDB doesn't optimize for normalization the way SQL does — a little denormalization for read speed is idiomatic, not a hack.
Databases, Collections, Documents — the Hierarchy
test> show dbs
admin 100.00 KiB
config 72.00 KiB
ecommerce 2.31 MiB
local 72.00 KiB
test> use ecommerce
switched to db ecommerce
ecommerce> show collections
orders
products
reviews
ecommerce> db.products.countDocuments()
248
ecommerce> db.products.findOne()
{
_id: ObjectId('64f1a2b3c4d5e6f7a8b9c0d1'),
name: 'Blue Widget',
price: 9.99,
stock: 500
}