Dissecting MongoDB's data hierarchy from database, collection, document, to field; understanding the BSON format and the rich data types within it; and breaking down the _id field and the 12-byte ObjectId that guarantees the uniqueness of every document.

In episode 1 you understood why MongoDB exists and when exactly to use it. Now it's time to go to the heart of the system: how MongoDB organizes and stores data. Without understanding this structure, you'll write queries that happen to work but never really understand what's happening behind the scenes.
Episode 2 is the most important technical foundation in the entire series. The roadmap: first we map the data hierarchy from database to field, second we dissect the BSON format along with the rich data types it supports, and third we unravel the mystery of _id and the ObjectId — the identity marker of every document. By the end of the episode, you'll be able to clearly imagine how a single document takes shape inside a MongoDB server.
If you're used to RDBMS, the easiest way to understand MongoDB is through analogy. MongoDB has four hierarchy levels:
| MongoDB level | RDBMS analogy | Example |
|---|---|---|
| Database | Database | belajar |
| Collection | Table | users |
| Document | Row | One user record |
| Field | Column | name, email |
The big difference is at the Document vs Row level: a row in an RDBMS must have a column structure identical to other rows, while a document in MongoDB may have a different structure. And more importantly — a Collection doesn't need to be declared in advance. You can write directly to a new collection and MongoDB will create it automatically.
A database is the top-level container. One MongoDB server can hold many databases. You can see the list with show dbs, and switch to or enter a database with use:
use belajar
show dbs
dbdb displays the name of the currently active database. An important note: a new database won't appear in show dbs until the first document is written to it — this is MongoDB's signature "lazy creation" behavior.
A Collection is a group of documents, analogous to a table. But unlike a table, a collection has no fixed schema. A Document is the smallest data unit actually stored — analogous to a row, but in the form of a flexible JSON structure. To see the existing collections:
show collections
db.users.countDocuments()The countDocuments function counts the number of documents in a collection. You'll use this function often for quick checks.
MongoDB doesn't store documents as JSON text, but as BSON (Binary JSON) — an encoded binary representation of JSON. Why binary? There are three main reasons:
Date, Decimal128, Binary, ObjectId, and UUID.When you write queries in mongosh, you're indeed typing JSON-like syntax — but behind the scenes, everything is converted to BSON before being stored. Here are the main BSON data types you need to know:
| BSON type | mongosh alias | Description |
|---|---|---|
| String | "teks" | UTF-8 string |
| Int32 | NumberInt(5) | 32-bit integer |
| Int64 | NumberLong(5) | 64-bit integer |
| Double | 5.25 | Floating point |
| Boolean | true / false | Logical value |
| Date | ISODate("2026-08-03T00:00:00Z") | Time |
| ObjectId | ObjectId("66...") | Unique 12-byte identity |
| Array | [1, 2, 3] | List of values |
| Embedded Document | { "address": { ... } } | Nested document |
| Binary | BinData(0, "...") | Binary data |
| Decimal128 | NumberDecimal("19.99") | High decimal precision |
| Null | null | Empty value |
Two BSON types most often cause bugs in the field:
NumberDecimal — for money, never use Double. The floating-point calculation 0.1 + 0.2 yields 0.30000000000000004. For monetary values, use NumberDecimal:
db.products.insertOne({ name: "Tas", price: NumberDecimal("99.99") })
db.products.findOne()ISODate — dates in MongoDB are always stored in UTC. When you insert ISODate("2026-08-03T00:00:00Z"), MongoDB stores it in UTC; conversion to local time zones happens on the application side.
Every MongoDB document must have a _id field that is unique within the collection. Two important rules:
_id at insert time, MongoDB automatically creates one as an ObjectId._id, its value can be anything (string, integer, UUID) as long as it's unique in the collection.db.users.insertOne({ _id: "user-001", name: "Arman" })
db.users.insertOne({ name: "Budi" })
db.users.find()Result: the first document has _id: "user-001" as you set it, and the second document gets an automatic ObjectId _id.
ObjectId is the default type for _id. It's 12 bytes long, composed of three parts, each with its own meaning:
| Bytes | Contents | Meaning |
|---|---|---|
| 1–4 | Timestamp (seconds since epoch) | When the document was created |
| 5–9 | Per-process random value | Uniqueness across processes |
| 10–12 | Incrementing counter | Uniqueness within the same process |
Because it contains a timestamp, you can extract the document's creation time directly from the _id without storing a separate createdAt field:
db.users.findOne()._id.getTimestamp()The timestamp + random + counter combination makes the ObjectId unique without needing server-to-server communication — this is important for distributed systems where many servers can create documents simultaneously without collisions.
Now let's apply all the concepts in one real example. We'll create a users collection with a document that leverages rich BSON types:
db.users.insertOne({
name: "Arman Dwi Pangestu",
age: NumberInt(27),
email: "arman@example.com",
balance: NumberDecimal("2500.50"),
isActive: true,
registeredAt: ISODate("2026-08-03T08:30:00Z"),
tags: ["backend", "mongodb"],
address: {
city: "Jakarta",
country: "Indonesia"
}
})Notice the combination of types inside a single document: string, Int32, Decimal128, boolean, Date, array, and embedded document — all coexisting naturally. This is something that isn't nearly as easy to do in an RDBMS.
Info
Use db.users.findOne() to see the document you just inserted. Notice that _id appears automatically as an ObjectId, and mongosh displays NumberDecimal("2500.50") and ISODate(...) in a readable form. This representation is exactly the BSON value stored — not just JSON text.
In episode 2 you understood MongoDB's data hierarchy: a Database contains Collections (analogous to tables), which contain Documents (analogous to rows) that hold Fields (analogous to columns). You also got to know the BSON format and the rich data types within it — including NumberDecimal for money and ISODate for time — and understood _id as the mandatory identity of every document, with the 12-byte ObjectId composed of a timestamp, random value, and counter.
Key takeaways:
NumberDecimal for money, Double for ordinary calculations._id; the default 12-byte ObjectId carries a timestamp.In the next episode, episode 3, we start touching real operations: Create & Read Operations. You'll write insertOne, insertMany, and bulkWrite to store data, then retrieve it back with find, findOne, and projection to control which fields are returned. See you in episode 3, and get ready to write your first data to MongoDB!