iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

BSON & Types

BSON (Binary JSON) is MongoDB’s on-disk and wire format. It adds richer types — ObjectId, Date, Decimal128, Binary — while staying close to JSON’s mental model. Knowing the types prevents the common “why is my money rounding?” bug.

Types, size limits, conversion, queries

EXAMPLE
// 1) Type cheat sheet
//   double            — 64-bit float (JavaScript Number)
//   string            — UTF-8
//   object / array    — nested document / array
//   binData           — raw bytes (uuid, file, etc.)
//   ObjectId          — 12-byte unique id (timestamp + machine + counter)
//   bool, date, null  — self-explanatory; date is millisecond-precision UTC
//   int32, long       — exact integers (no float rounding)
//   decimal           — Decimal128: exact decimal for money
//   regex             — server-side regex object
//   minKey, maxKey    — sentinel sort values

// 2) Insert with proper types (Node driver)
import { MongoClient, ObjectId, Decimal128, Binary } from 'mongodb';
const client = new MongoClient(process.env.MONGO_URL);
await client.connect();
const db = client.db('shop');

await db.collection('orders').insertOne({
    _id:        new ObjectId(),
    customerId: new ObjectId('64999...'),
    totalCents: 4999,                       // store money as integer cents
    totalUSD:   Decimal128.fromString('49.99'),  // OR Decimal128 for arbitrary precision
    createdAt:  new Date(),                  // BSON Date, not a string
    status:     'paid',
    attachment: new Binary(Buffer.from(pdfBytes)),
    paid:       true,
});

// 3) Why integer cents?
// JS Number is double-precision float. 0.1 + 0.2 !== 0.3 in production code.
// MongoDB stores 'double' for plain numbers; same rounding bugs apply.
// Either: integer cents, OR Decimal128.

// 4) ObjectId anatomy
const id = new ObjectId();
id.toHexString();                              // 'e4d5c6...' 24 chars
id.getTimestamp();                              // creation Date
ObjectId.isValid('abc');                        // false

// 5) Dates
// • new Date() in JS -> BSON Date (milliseconds since epoch, UTC)
// • Avoid storing date STRINGS — can't range-query, sort fails for '2024-01-01' vs ISO 8601 mixes
// • Use ISODate('2024-01-15T03:21:00Z') in mongosh
const since = new Date('2024-01-01');
await db.collection('orders').find({ createdAt: { $gte: since } }).toArray();

// 6) Binary subtypes
const uuidBin = Binary.createFromHexString('1c5b03d7...', Binary.SUBTYPE_UUID);
await db.collection('files').insertOne({ uuid: uuidBin, data: new Binary(buf) });

// SUBTYPES: 0x00 generic, 0x02 deprecated, 0x04 UUID, 0x05 MD5, 0x80 user-defined

// 7) Decimal128 — exact decimal arithmetic
const price = Decimal128.fromString('19.95');
await db.collection('products').insertOne({ name: 'shoe', price });
// In queries: { price: Decimal128.fromString('19.95') }
// Don't compare against a JS Number — different BSON types.

// 8) Document size limits
// • Single document: 16 MB max
// • Embedded array: bounded by doc size — millions of elements blow it up
// • For large blobs (PDFs, images, videos): GridFS, S3, or object storage

// 9) Strict vs extended JSON (file import / export)
// Strict   — { '_id': { '$oid': '64999...' }, 'created': { '$date': '2024-01-01T00:00:00Z' } }
// Canonical — preserves all type info
// Relaxed  — loses some precision (uses ISO date string)

// Use mongoexport/mongoimport with --type=json --jsonArray for portability.

// 10) BSON in Node — bson package
import { serialize, deserialize, EJSON, ObjectId } from 'bson';

const doc = { _id: new ObjectId(), name: 'Mara', age: 30 };
const bytes = serialize(doc);             // Buffer of BSON bytes
const back  = deserialize(bytes);         // back to JS

// EJSON helpers for safe stringify with type metadata
const json = EJSON.stringify(doc, { relaxed: false });
const rehydrated = EJSON.parse(json, { relaxed: false });

// 11) Compare values with the right shape
// MongoDB compares values by BSON type FIRST, then by value within type.
// '5' != 5 != Decimal128('5') — all different types.
// Use $type to filter by type:
await db.collection('users').find({ age: { $type: 'number' } }).toArray();

// 12) Common bugs
// • Storing money as Number → rounding bugs; switch to integer cents or Decimal128
// • Storing dates as strings → can't sort/range; ALWAYS new Date()
// • Passing a string id to find({ _id }) where _id is ObjectId → no match
// • Storing huge embedded arrays → hit 16 MB limit; reference instead
// • Mixing BigInt with Number in queries — BSON has Long (int64); use that
// • Forgetting Binary subtype for UUIDs → they store but legacy tools can't read
// • Returning Decimal128 to a JSON API → serialise to a string with toString()

Why it matters

Use the right BSON type up front: ObjectId for ids, Date for timestamps, integer cents or Decimal128 for money, Binary with the UUID subtype for UUIDs. Keep documents well under the 16 MB limit by referencing large blobs (S3 / GridFS) instead of embedding them.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
// BSON adds binary types over JSON: ObjectId, Date, Decimal128, …
{ _id: ObjectId('...'), createdAt: ISODate('2026-01-01') }
Try it Yourself »

Discussion

Loading…