Skip to content
ExponaBLOG
MONGODB, NOSQL & DATA MODELING · DAY 004 / 100

Documents and collections explained: a step-by-step guide

The building blocks of MongoDB.

Expona Admin7 min read

Why understanding MongoDB's document and collection structure matters

If you're building a system that needs flexibility, scale, and reasonable query performance, MongoDB's document model is one of the first things you'll need to master. Moving from relational databases to MongoDB isn't just a syntax change—it's a different way of thinking about data storage. This guide, documents and collections explained: a step-by-step guide, walks you through the foundational concepts that make MongoDB work, with practical examples you'll recognize from real production systems.

Documents are BSON objects with an _id field

At the heart of MongoDB is the document. A document is a BSON (Binary JSON) object—essentially a map of key-value pairs that can hold nested structures, arrays, and typed data. Unlike a row in a SQL table, a MongoDB document is self-describing and flexible.

Every document must have an _id field. MongoDB generates this automatically as an ObjectId if you don't provide one, but you can set it to any unique value. The _id acts as the primary key, ensuring uniqueness within a collection.

Here's a concrete example from an e-commerce system:

javascript
{
  "_id": ObjectId("507f1f77bcf86cd799439011"),
  "email": "alice@example.com",
  "name": "Alice Chen",
  "created_at": ISODate("2024-01-15T10:30:00Z"),
  "is_premium": true,
  "tags": ["early-adopter", "verified"],
  "address": {
    "street": "123 Oak Lane",
    "city": "Portland",
    "zip": "97214"
  },
  "orders_count": 42,
  "last_order_date": ISODate("2024-01-20T14:22:00Z")
}

Notice how much we're storing in a single document. We have scalars (email, name), arrays (tags), nested objects (address), and dates. All of this is valid BSON. In a relational database, this data would be spread across multiple tables. In MongoDB, it lives together.

BSON supports a wide range of types: strings, numbers (int32, int64, double), dates, binaries, arrays, embedded documents, ObjectIds, booleans, nulls, regular expressions, and more. This richness means you rarely need to serialize or deserialize data into JSON before storing it.

Collections group similar documents

A collection is simply a group of documents, roughly equivalent to a table in relational databases. But unlike a table, a collection doesn't enforce a schema by default. You can insert documents with different structures into the same collection.

Think of a collection as a logical grouping based on purpose, not structure. In our e-commerce example, we'd have a users collection, an orders collection, a products collection, and so on.

Here's what that might look like:

javascript
// Collection: users
db.users.insertOne({
  "_id": ObjectId("507f1f77bcf86cd799439011"),
  "email": "alice@example.com",
  "name": "Alice Chen"
})

// Collection: orders
db.orders.insertOne({
  "_id": ObjectId("507f1f77bcf86cd799439012"),
  "user_id": ObjectId("507f1f77bcf86cd799439011"),
  "total": 189.99,
  "items": [
    { "product_id": "P001", "qty": 2, "price": 45.00 },
    { "product_id": "P002", "qty": 1, "price": 99.99 }
  ],
  "status": "shipped",
  "created_at": ISODate("2024-01-20T14:22:00Z")
})

// Collection: products
db.products.insertOne({
  "_id": "P001",
  "name": "Widget Pro",
  "price": 45.00,
  "stock": 500,
  "category": "widgets"
})

Collections exist implicitly—you don't need to create them ahead of time. When you insert into a collection that doesn't exist, MongoDB creates it for you. You can also explicitly create collections with options like validation rules or collation settings, but for basic use, just inserting data is enough.

Within a collection, you'd typically maintain consistency by documenting what fields are expected. This is especially important in a team where multiple developers are working with the same data. Many teams use TypeScript interfaces or JSON schema validation to enforce this discipline.

No fixed schema by default

This is where MongoDB differs fundamentally from SQL databases. MongoDB is schemaless by default, meaning you can insert documents with different structures into the same collection. This flexibility is powerful but requires discipline.

Consider a scenario where you're iterating on a feature. Your user document initially has just email and name. Later, you add phone_number and preferences. In MongoDB, you don't need to migrate existing documents—new ones get the new fields, old ones don't. Your code just handles both cases.

javascript
// Old document format
{ "_id": 1, "email": "old@example.com", "name": "Old User" }

// New document format
{ "_id": 2, "email": "new@example.com", "name": "New User", "phone_number": "555-1234", "preferences": { "newsletter": true } }

// Both can coexist in the same collection

However, schemaless doesn't mean schema-free. You still need to think carefully about what data belongs in your documents. The flexibility should be intentional, not accidental. When you're querying or updating, you need to know what fields might exist and handle missing ones gracefully.

In production systems, most teams implement schema validation anyway—either through MongoDB's built-in JSON schema validation or through application-level validation. This catches mistakes early and documents intent.

javascript
// Example: enforcing schema validation at the collection level
db.createCollection("users", {
  validator: {
    $jsonSchema: {
      bsonType: "object",
      required: ["email", "name"],
      properties: {
        email: { bsonType: "string" },
        name: { bsonType: "string" },
        phone_number: { bsonType: "string" },
        preferences: { bsonType: "object" }
      }
    }
  }
});

This validation is optional but recommended. It gives you the flexibility of a schemaless database with safeguards against data quality issues.

16 MB maximum document size

MongoDB enforces a hard limit: no single document can exceed 16 MB. This is a practical constraint that forces good data modeling decisions.

In practice, this rarely bites you unless you're doing something unusual. A document with 10,000 fields, or one that embeds thousands of related records, will hit this limit. For most use cases—user profiles, order records, product details—you won't come close.

When you do approach the limit, the solution is denormalization or bucketing. For example, if you're storing a user with a huge history of events, you might:

  • Keep recent events in the user document, and archive older ones to a separate user_events collection
  • Use bucketing: store events in groups, e.g., { user_id, month: "2024-01", events: [...] }
  • Store just references in the main document and join at query time

Here's a real scenario: storing conversation history in a chat system. If you tried to embed all messages in a single user document, it would blow up quickly. Instead:

javascript
// Don't do this for large message histories
{ _id: "user123", messages: [ ... 100k messages ... ] }  // Will exceed 16 MB

// Instead, keep messages in a separate collection
db.conversations.insertOne({
  "_id": ObjectId(),
  "user_id": "user123",
  "participant_id": "user456",
  "created_at": ISODate("2024-01-01T00:00:00Z"),
  "last_message_at": ISODate("2024-01-20T14:22:00Z")
})

db.messages.insertMany([
  { "_id": 1, "conversation_id": ObjectId(...), "from": "user123", "text": "Hi", "sent_at": ISODate(...) },
  { "_id": 2, "conversation_id": ObjectId(...), "from": "user456", "text": "Hello", "sent_at": ISODate(...) }
])

// Query: find recent messages for a conversation
db.messages.find({ "conversation_id": ObjectId(...) }).sort({ sent_at: -1 }).limit(50)

The 16 MB limit isn't a flaw—it's a design feature that encourages horizontal scaling. Rather than storing everything about a user in one giant document, you split it logically across collections, each indexed and queryable independently.

Common mistakes

Mistake 1: Treating documents like database rows. Documents can nest and hold arrays in ways SQL rows cannot. Resist the urge to normalize everything. Sometimes embedding is the right call. Ask: "Will I always need these together, and are they small enough to fit in 16 MB?" If yes, embed.

Mistake 2: Ignoring the _id field. Many developers assume MongoDB will handle it invisibly. It won't. If you're building a distributed system or migrating data, you need a strategy for _id generation. ObjectId is fine for most cases, but UUID or application-specific IDs are valid too.

Mistake 3: Relying entirely on schemaless flexibility. Yes, you can insert any structure, but that doesn't mean you should. Document your expected fields. Validate at the application level or use JSON schema. Schema discipline prevents bugs where code expects a field that doesn't exist.

Mistake 4: Assuming there's no performance difference between document structures. A document with 500 fields where you only query 5 will be slower than one with just those 5 fields. Keep documents lean. Store only what you need for common access patterns.

Key takeaways

  • Documents are BSON objects that function as the unit of data in MongoDB. Every document has an _id that uniquely identifies it within its collection.
  • Collections are logical groupings of documents, similar to tables but without enforced schema constraints by default.
  • Flexibility is MongoDB's strength, but structure your documents intentionally. Use application-level or schema validation to maintain data quality.
  • The 16 MB document limit is a design boundary that encourages proper data modeling. When approaching it, use separate collections or bucketing strategies.
  • Embedding related data in a single document is often the right choice in MongoDB, unlike relational databases. This denormalization reduces round-trips and simplifies queries for common access patterns.
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles