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

BSON vs JSON

JSONBSON (MongoDB)
null, boolean, number, string, array, objectSame + Date, ObjectId, int/long, decimal, binary
Numbers are doubles onlyInt32, Int64, Double, Decimal128
No date typeISODate("2026-05-28T12:00:00Z")

Drivers (PyMongo, Node) map language types to BSON automatically.

Your First Collection and Document

MongoDB returns:

javascript
{
  acknowledged: true,
  insertedId: ObjectId("665f0123456789abcdef01234")
}

Code explanation:

  • insertedId is the auto-generated _id
  • profile is a nested subdocument—no separate table
  • roles is an array field

Read it back:

javascript
db.users.findOne({ email: "ada@example.com" })

The _id Field

Every document must have _id. It is unique within the collection.

ApproachWhen
Auto ObjectIdDefault—good for most apps
Custom string/UUIDIdempotency, external system IDs
Compound business keyRare; often use unique index instead

Custom _id:

javascript
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.

javascript
ObjectId("665f0123456789abcdef01234").getTimestamp()

Common BSON Types in mongosh

javascript
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 NumberDecimal for money—not binary floating double
  • new Date() stores UTC; display time zones in the application layer
  • null is 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 .
  • _id is reserved
  • Maximum document size: 16 MB
  • Nesting depth limit: 100 levels (rarely hit)

Bad field name (will error on insert):

javascript
// Invalid — do not use dots in keys
db.bad.insertOne({ "address.city": "Paris" })

Good:

javascript
db.users.insertOne({ address: { city: "Paris" } })

Insert Many Documents

javascript
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 rowMongoDB document
Fixed columns from CREATE TABLEFlexible fields per document
JOIN for related dataEmbed or reference by _id
AUTO_INCREMENT idObjectId or custom _id

Relational modeling — MySQL tables; document modeling starts in chapter 9.

Inspect Collection Stats

javascript
db.users.stats()
db.users.countDocuments()
db.users.estimatedDocumentCount()

countDocuments(filter) respects filters; estimatedDocumentCount() is faster but approximate.

Common Mistakes

MistakeSymptomFix
String _id vs ObjectId in queryNo matchUse same type: ObjectId("...")
Storing dates as stringsWrong sort orderUse Date type
Huge embedded arrays16 MB limit, slow loadsSeparate collection
Dot in field nameWrite errorNest object instead

Post-Chapter Checklist

  • You inserted documents with insertOne and insertMany
  • You understand auto _id vs custom _id
  • You used Date and NumberDecimal appropriately
  • 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.