BSON and Document Basics
Introduction
MongoDB stores BSON (Binary JSON)—a binary format that extends JSON with extra types like ObjectId, Date, and Decimal128. Understanding BSON types and document rules prevents subtle bugs in queries, indexes, and API serialization. This chapter inserts your first documents in mongosh and explains _id, nesting limits, and field naming rules.
Prerequisites
- Installation and mongosh — running server and shell
- Familiarity with JSON objects and arrays
BSON vs JSON
| JSON | BSON (MongoDB) |
|---|---|
null, boolean, number, string, array, object | Same + Date, ObjectId, int/long, decimal, binary |
| Numbers are doubles only | Int32, Int64, Double, Decimal128 |
| No date type | ISODate("2026-05-28T12:00:00Z") |
Drivers (PyMongo, Node) map language types to BSON automatically.
Your First Collection and Document
MongoDB returns:
{
acknowledged: true,
insertedId: ObjectId("665f0123456789abcdef01234")
}Code explanation:
insertedIdis the auto-generated_idprofileis a nested subdocument—no separate tablerolesis an array field
Read it back:
db.users.findOne({ email: "ada@example.com" })The _id Field
Every document must have _id. It is unique within the collection.
| Approach | When |
|---|---|
Auto ObjectId | Default—good for most apps |
| Custom string/UUID | Idempotency, external system IDs |
| Compound business key | Rare; often use unique index instead |
Custom _id:
db.products.insertOne({
_id: "sku-1001",
name: "Mechanical Keyboard",
price: NumberDecimal("129.99")
})Duplicate _id on insert throws E11000 duplicate key.
ObjectId structure (concept)
12 bytes: timestamp + random + counter. Useful for rough sort-by-creation without a separate field—do not rely on it for security.
ObjectId("665f0123456789abcdef01234").getTimestamp()Common BSON Types in mongosh
db.types_demo.insertOne({
str: "hello",
bool: true,
int: NumberInt(42),
long: NumberLong(9007199254740991),
double: 3.14,
decimal: NumberDecimal("19.99"),
createdAt: new Date(),
metadata: null,
payload: BinData(0, "SGVsbG8=")
})Code explanation:
- Use
NumberDecimalfor money—not binary floatingdouble new Date()stores UTC; display time zones in the application layernullis a valid stored value; missing field ≠ null
Warning
Avoid Floating Point for Money
19.99 as double can become 19.989999999. Store currency as Decimal128 or integer cents.
Field Names and Document Limits
Rules:
- Field names cannot contain
$(except operators in updates) or. _idis reserved- Maximum document size: 16 MB
- Nesting depth limit: 100 levels (rarely hit)
Bad field name (will error on insert):
// Invalid — do not use dots in keys
db.bad.insertOne({ "address.city": "Paris" })Good:
db.users.insertOne({ address: { city: "Paris" } })Insert Many Documents
db.tags.insertMany([
{ name: "database", slug: "database" },
{ name: "nosql", slug: "nosql" },
{ name: "mongodb", slug: "mongodb" }
])Ordered vs unordered batch behavior is covered in Insert and Import Export.
Compare to MySQL Row
| MySQL row | MongoDB document |
|---|---|
Fixed columns from CREATE TABLE | Flexible fields per document |
JOIN for related data | Embed or reference by _id |
AUTO_INCREMENT id | ObjectId or custom _id |
Relational modeling — MySQL tables; document modeling starts in chapter 9.
Inspect Collection Stats
db.users.stats()
db.users.countDocuments()
db.users.estimatedDocumentCount()countDocuments(filter) respects filters; estimatedDocumentCount() is faster but approximate.
Common Mistakes
| Mistake | Symptom | Fix |
|---|---|---|
String _id vs ObjectId in query | No match | Use same type: ObjectId("...") |
| Storing dates as strings | Wrong sort order | Use Date type |
| Huge embedded arrays | 16 MB limit, slow loads | Separate collection |
| Dot in field name | Write error | Nest object instead |
Post-Chapter Checklist
- You inserted documents with
insertOneandinsertMany - You understand auto
_idvs custom_id - You used
DateandNumberDecimalappropriately - You know the 16 MB document size limit
FAQ
Is BSON the same as JSON?
BSON is binary-encoded JSON-like data with extra types. Wire protocol uses BSON; mongosh displays JSON-like syntax.
Can documents in one collection differ?
Yes—flexible schema. Production apps still agree on conventions and optional schema validation.
UUID as _id?
Use UUID() in mongosh or string UUID in drivers—ensure unique index if not _id.
How to pretty-print in mongosh?
.pretty() on cursor: db.users.find().limit(1).pretty()
Delete test data?
db.users.drop() removes the whole collection—dev only.