Documents and BSON: The Cornerstone of MongoDB Data

BSON is MongoDB's data format—it extends the capabilities of JSON and supports native types such as Date, Binary, and Decimal128.

This course provides an in-depth understanding of the BSON data format, the internal structure of ObjectId, and the field type system, and teaches best practices for document design.

1. What You'll Learn


2. A True Story of a Full-Stack Engineer

(1) Pain Point: Dates are converted to strings when JSON is stored in MongoDB

Charlie is a full-stack Node.js engineer who is migrating MySQL data to MongoDB:

"I converted the order data from MySQL to JSON and stored it in MongoDB, only to find that all dates had been converted to the string new Date(), which couldn't be parsed; the precision of the amounts was lost (0.1 + 0.2 ≠ 0.3); and binary profile pictures couldn't be stored at all."

He serialized the order data using JSON.stringify(), which resulted in the loss of type information:

JAVASCRIPT
// ❌ Error:JSON.stringify Type of Loss
const order = {
  createdAt: new Date(),         // Date Object
  total: new Number('0.30'),     // Decimal128 A more precise type should be used.
  avatar: Buffer.from('...'),    // Binary Avatar
  _id: new ObjectId()            // MongoDB Expected ObjectId
};

const json = JSON.stringify(order);
// {"createdAt":"2026-07-01T...","total":0.3,"avatar":"...","_id":"..."}
//         ^^^^^^^^^^^^^^^^ String        ^ Floating-point numbers(Loss of Accuracy) ^ String(Cannot be restored)

(2) The BSON Solution

MongoDB stores data directly in BSON (Binary JSON) format, preserving all type information.

JAVASCRIPT
// ✅ Correct:mongoose Direct Operation BSON Type
const OrderSchema = new mongoose.Schema({
  createdAt: { type: Date, default: Date.now },           // BSON Date
  total: { type: mongoose.Schema.Types.Decimal128 },        // BSON Decimal128(Accurate)
  avatar: { type: Buffer },                                 // BSON Binary
  _id: { type: mongoose.Schema.Types.ObjectId, auto: true } // BSON ObjectId
});

const order = await Order.create({
  total: mongoose.Types.Decimal128.fromString('0.30'),
  avatar: fs.readFileSync('avatar.jpg')
});

(3) Revenue

Dimension JSON BSON
Date Type String (requires manual parsing) Native Date (millisecond precision)
Numeric Precision Floating-point (loss of precision) Decimal128 (34-bit precision)
Binary Data Not supported Native Binary
Field Order Unordered Ordered (Important!)
Size and Resource Usage More compact Slightly larger (5–15% larger)

3. BSON Data Format

Concept Overview: BSON (Binary JSON) is a binary serialization format specific to MongoDB and is a superset of JSON. While JSON has only six data types (string, number, boolean, null, array, and object), BSON supports more than 12 types, including database-essential types such as Date, Binary, ObjectId, and Decimal128. The core advantages of BSON are: rich data types, ordered fields, and extremely fast parsing.

How It Works: BSON documents are stored in binary format. Each document begins with a 4-byte length header, followed by a sequence of key-value pairs, and ends with 0x00. Unlike JSON’s text-based parsing, BSON’s length header allows for the rapid skipping of unnecessary fields (similar to the “fixed-length header” design in binary protocols), resulting in parsing performance that is 3–5 times faster than JSON. The trade-off is a 5–15% increase in space overhead (to store type and length information).

100%
graph TB
    subgraph "BSON Internal Structure of the Document"
        A[4 Byte<br/>Total length of the document] --> B[Type Code 1B<br/>+ Field Name<br/>+ Value]
        B --> C[Type Code 1B<br/>+ Field Name<br/>+ Value]
        C --> D[...More key-value pairs...]
        D --> E[0x00<br/>Closing tag]
    end
    
    style A fill:#cce5ff
Dimension JSON BSON
Type Text Format Binary Format
Readability ✅ Human-readable ❌ Binary
Performance Slow resolution Extremely fast resolution (3–5x)
Wide variety 6 types 12+ types
Field Order Unordered Ordered
Space More compact 5–15% more

(1) What is BSON?

BSON (Binary JSON) is the binary serialization format used by MongoDB. Its features include:

100%
graph LR
    A[JavaScript Object] -->|JSON.stringify| B[JSON Text]
    A -->|BSON Serialization| C[BSON Binary]

    B --> D[Transmission / Storage]
    C --> D

    style C fill:#d4edda
Dimension JSON BSON
Type Text Format Binary Format
Readability ✅ Human-readable ❌ Binary
Performance Slow resolution Extremely fast resolution
Wide variety 6 types 12+ types
Field Order Unordered Ordered
Space More compact 5–15% more

(2) BSON Document Structure

Key Points Analysis:

  1. BSON field insertion order—this is critical for indexing and query optimization in MongoDB
  2. Each field is preceded by a 1-byte type code, allowing BSON to distinguish between Date and String (unlike JSON).
  3. Nested documents and arrays are stored recursively in BSON, with a maximum nesting depth of 100 levels.
  4. The _id field is always at the beginning of the document, optimizing query performance
JAVASCRIPT
// One BSON Internal Representation of a Document(Simplify)
{
  _id: ObjectId("507f1f77bcf86cd799439011"),    // 12 Byte ObjectId
  name: "Alice",                                // String(UTF-8)
  age: 28,                                      // Int32
  balance: Decimal128("12345.6789"),            // Decimal128(High precision)
  joinedAt: ISODate("2026-07-01T10:00:00Z"),    // Date(64-bit Integer)
  isActive: true,                               // Boolean
  hobbies: ["reading", "coding", "hiking"],     // Array
  address: {                                    // Embedded Document
    city: "Tokyo",
    country: "Japan"
  },
  profile: null,                                // Null
  avatar: BinData(0, "..."),                    // Binary
  // Field Order:BSON Preserve the insertion order of fields(JSON No guarantee)
}

▶ Example 1: Viewing BSON Details in mongosh

JAVASCRIPT
// Insert a document
db.users.insertOne({
  name: "Alice",
  age: 28,
  joinedAt: new Date(),
  balance: NumberDecimal("12345.6789"),
  address: { city: "Tokyo", country: "Japan" }
});

// View BSON Details(Usage bsonSon Function)
db.users.findOne({ name: "Alice" });
// {
//   _id: ObjectId('507f1f77bcf86cd799439011'),
//   name: 'Alice',
//   age: 28,
//   joinedAt: ISODate('2026-07-01T10:00:00.000Z'),
//   balance: NumberDecimal('12345.6789'),
//   address: { city: 'Tokyo', country: 'Japan' }
// }

// View Field Types
const doc = db.users.findOne({ name: "Alice" });
print(typeof doc.age);             // number
print(doc.joinedAt instanceof Date); // true

4. The ObjectId Primary Key Mechanism

Concept Explanation: ObjectId is MongoDB’s default primary key type, consisting of a 12-byte (96-bit) binary value. Unlike the auto-incrementing integer primary keys found in traditional databases, ObjectId employs a distributed design—composed of a timestamp, a random value, and a counter—ensuring global uniqueness without the need for centralized coordination. Another major advantage of ObjectId is that it inherently includes the creation time, which can be extracted directly without the need for additional fields.

How It Works: The 12 bytes of an ObjectId are divided into three segments: the first 4 bytes are a Unix timestamp (accurate to the second), the middle 5 bytes are a random value (determined by the machine ID and process ID when first generated, and remain unchanged thereafter), and the last 3 bytes are an incrementing counter (which increments from a random starting value within the same second). This design allows a single process to generate approximately 16.77 million unique ObjectIds within a single second.

100%
graph LR
    A[ObjectId 12 Byte] --> B[4 Byte Timestamp<br/>Accuracy to the second]
    A --> C[5 Random Byte Values<br/>Machine/Unique Process]
    A --> D[3 Byte-Increment Counter<br/>Unique within a single second]

    style A fill:#cce5ff
Section Length Content Purpose
Timestamp 4 bytes Unix timestamp (seconds) Creation time can be extracted
Random 5 bytes Machine ID + Process ID Unique across processes
Counter 3 bytes Incremental counter Unique within a single second
_id Strategy Advantages Disadvantages Use Cases
Auto-generated ObjectId Distributed, unique, timestamped, naturally ordered 12 bytes (relatively large) General-purpose (default)
String Business Keys Clear semantics, good readability Must be manually ensured to be unique Order number, SKU
Auto-incrementing integers Compact, readable Requires a counter set; not suitable for sharding Legacy systems
UUID Globally unique 16 bytes, unordered Unique across systems

(1) What is an ObjectId?

ObjectId is MongoDB's default primary key type, a 12-byte (96-bit) binary value:

100%
graph LR
    A[ObjectId 12 Byte] --> B[4 Byte Timestamp<br/>Accuracy to the second]
    A --> C[5 Random Byte Values<br/>Machine/Unique Process]
    A --> D[3 Byte-Increment Counter<br/>Unique within a single second]

    style A fill:#cce5ff
Section Length Content Purpose
Timestamp 4 bytes Unix timestamp (seconds) Creation time can be extracted
Random 5 bytes Machine ID + Process ID Unique across processes
Counter 3 bytes Incremental counter Unique within a single second

(2) Advantages of ObjectId

Key Points Analysis:

  1. The timestamp portion of the ObjectId naturally sorts entries by insertion time—allowing queries by time range without the need for additional createdAt indexes.
  2. A 5-byte random value is generated and cached when the process starts, ensuring uniqueness across processes (2^40 ≈ 1 trillion possibilities).
  3. The 3-byte counter increments within a single second, generating 2^24 ≈ 16.77 million unique IDs per second.
  4. The getTimestamp() method can extract the creation time directly from the ObjectId without the need for additional queries.
JAVASCRIPT
// Create ObjectId in mongosh
const id1 = ObjectId();              // Automatically Generated
const id2 = ObjectId("507f1f77bcf86cd799439011");  // Generate from a string

// Retrieve the creation time(Key Advantages!)
id2.getTimestamp();
// ISODate("2012-10-17T20:46:11.000Z")

// Use in Node.js with mongoose
const mongoose = require('mongoose');
const id = new mongoose.Types.ObjectId();
console.log(id.getTimestamp());  // 2026-07-01T10:00:00.000Z

(3) Ensuring the Uniqueness of ObjectId

100%
graph TB
    A[Client A<br/>Generated in the same second ID] --> A1[time=1000<br/>random=ABC<br/>counter=1]
    A --> A2[time=1000<br/>random=ABC<br/>counter=2]
    A --> A3[time=1000<br/>random=ABC<br/>counter=3]

    B[Client B<br/>Generated in the same second ID] --> B1[time=1000<br/>random=DEF<br/>counter=1]
    B --> B2[time=1000<br/>random=DEF<br/>counter=2]

    style A1 fill:#d4edda
    style B1 fill:#d4edda

▶ Example 2: Extracting the ObjectId Timestamp

JAVASCRIPT
// === In mongosh ===
const products = db.products.find().toArray();
products.forEach(p => {
  print(`Product ${p._id} created at ${p._id.getTimestamp()}`);
});

// === by ObjectId Time Range Query ===
const startOfDay = ObjectId.createFromTime(
  Math.floor(new Date('2026-07-01').getTime() / 1000)
);
const endOfDay = ObjectId.createFromTime(
  Math.floor(new Date('2026-07-02').getTime() / 1000)
);

db.products.find({
  _id: { $gte: startOfDay, $lt: endOfDay }
});

// === Node.js / mongoose ===
const Product = mongoose.model('Product', productSchema);
const products = await Product.find({
  _id: {
    $gte: mongoose.Types.ObjectId.createFromTime(
      Math.floor(Date.parse('2026-07-01') / 1000)
    ),
    $lt: mongoose.Types.ObjectId.createFromTime(
      Math.floor(Date.parse('2026-07-02') / 1000)
    )
  }
});

5. BSON Data Types

Concept Explanation: BSON supports more than 12 data types, far exceeding JSON’s 6. The most significant difference lies in numeric types—JSON has only one numeric type (Number, which is IEEE 754 double-precision floating-point), while BSON provides four numeric types: Double, Int32, Int64 (Long), and Decimal128. Selecting the wrong numeric type can result in a loss of precision (e.g., in monetary calculations 0.1 + 0.2 ≠ 0.3).

Use Cases: Financial amounts must use Decimal128 (34-digit decimal precision); counters use Int32; large integer IDs use Long; and scientific calculations and statistics use Double. The Date type is stored in BSON as a 64-bit millisecond timestamp, which is fundamentally different from JSON’s string-based dates.

Type Type Code Example Purpose
Double 1 3.14, 0.1+0.2 Floating-point (Default Number)
String 2 "Alice" UTF-8 string
Object 3 { key: "value" } Nested Document
Array 4 [1, 2, 3] Array
Binary data 5 BinData(0, "...") Binary data (images, files)
Undefined 6 undefined Not recommended
ObjectId 7 ObjectId("...") Default primary key
Boolean 8 true, false Boolean
Date 9 ISODate("...") Date and Time
Null 10 null Empty value
Regular Expression 11 /pattern/i Regular Expression
32-bit Integer 16 NumberInt(123) 32-bit integer
64-bit Integer 18 NumberLong(123) 64-bit Integer (BigInt)
Decimal128 19 NumberDecimal("0.30") High-precision decimals (finance)
MinKey/MaxKey -1 / 127 MinKey(), MaxKey() Comparison boundary

(1) 12 BSON Data Types

(2) Selecting a Numeric Type

Concept Explanation: Choosing the numeric data type is the most critical decision when working with BSON data types. JSON has only one Number type (double-precision floating-point), which leads to the classic problem of precision loss in financial calculations: 0.1 + 0.2 = 0.30000000000000004. BSON’s Decimal128 type solves this problem by providing 34-bit decimal precision, making it suitable for scenarios requiring precise calculations, such as amounts and tax rates.

Numeric Type Precision Range Storage Size Use Cases
Double 15–17 significant digits ±1.7×10^308 8 bytes Scientific computing, statistics, graphics
Int32 Exact -2^31 ~ 2^31-1 4 bytes General-purpose counting, inventory
Int64/Long Precision -2^63 ~ 2^63-1 8 bytes Large integer IDs, timestamps
Decimal128 34-bit decimal ±10^6145 16 bytes Financial Amount (Recommended)
100%
graph TB
    A[MongoDB Numeric Types] --> B[Double<br/>Default]
    A --> C[Int32<br/>32 Integer]
    A --> D[Long<br/>64 Integer]
    A --> E[Decimal128<br/>34 Decimal place]

    B --> B1[Applicable:Scientific Computing、Statistics]
    C --> C1[Applicable:Routine Count]
    D --> D1[Applicable:Large integers ID]
    E --> E1[Applicable:Finance、Amount]

    style E fill:#d4edda

▶ Example 3: Working with Numeric Types

JAVASCRIPT
// === Double(Default)===
db.products.insertOne({
  sku: "PHONE-001",
  price: 599.99  // Save as Double
});

// === Decimal128(Financial Recommendations)===
db.accounts.insertOne({
  balance: NumberDecimal("1234567890.12345678901234567890")
  // Precise Storage,No loss of precision
});

// === Int32(Count)===
db.products.insertOne({
  sku: "BOOK-001",
  stock: NumberInt(150)
});

// === Long(Large integers ID)===
db.orders.insertOne({
  _id: NumberLong("1700000000000")  // Timestamps as ID
});

// === JavaScript Processing Decimal128 ===
const account = await Account.findOne({});
console.log(account.balance.toString());  // "1234567890.12345678901234567890"

// === Number The Precision Trap ===
0.1 + 0.2;                          // 0.30000000000000004 ❌
NumberDecimal("0.1") + NumberDecimal("0.2"); // NumberDecimal("0.3") ✅

6. Field Naming Conventions

Concept Explanation: Field naming may seem like a minor detail, but it has a significant impact on team collaboration and long-term maintenance. MongoDB imposes three strict restrictions on field names (they cannot start with $, cannot contain ., and cannot be an empty string), as well as several soft recommendations (camelCase is recommended, avoid reserved words, and limit length). A consistent naming convention is the foundation of database maintainability.

Usage Scenarios: The JavaScript/TypeScript ecosystem recommends camelCase (consistent with code variable names), while the Python/SQL ecosystem recommends snake_case (consistent with database column names). In the MongoDB + Mongoose tech stack, camelCase is recommended for database field names, with snake_case output at the API layer via Mongoose’s toJSON conversion.

Naming Style Example Advantages Disadvantages Recommendation
camelCase firstName Native support in JS/TS Not SQL-friendly ⭐⭐⭐ (Recommended)
snake_case first_name SQL/Python friendly Needs quotes in JS ⭐⭐
kebab-case first-name URL-friendly Requires quotes in MongoDB

(1) MongoDB Field Naming Conventions

Valid naming:

JAVASCRIPT
// ✅ Valid field names
db.users.insertOne({
  firstName: "Alice",          // Hump-style
  first_name: "Alice",         // Snake-like
  "first-name": "Alice",       // kebab-case(Quotation marks are required)
  "user 1": "Alice",           // Contains spaces(Quotation marks are required)
  age28: 28                     // Ending in a number
});

// ❌ Invalid field name
db.users.insertOne({
  $name: "Alice",              // starts with $ ❌
  "user.name": "Alice",        // contains . ❌
  "": "Alice"                  // Empty string ❌
});

(2) Comparison of Three Naming Conventions

Style Example Advantages Disadvantages
camelCase firstName Native support in JS/TS Not SQL-friendly
snake_case first_name SQL/Python friendly Needs quotes in JS
kebab-case first-name URL-friendly Requires quotes in MongoDB

(3) Recommendation: camelCase + MongoDB official style

JAVASCRIPT
// ✅ Recommended Styles:camelCase
db.users.insertOne({
  firstName: "Alice",
  lastName: "Smith",
  emailAddress: "alice@example.com",
  dateOfBirth: new Date("1998-01-01"),
  isActive: true,
  totalSpent: NumberDecimal("1234.56")
});

▶ Example 4: Mongoose Schema Naming Conventions

JAVASCRIPT
// mongoose Automatically convert camelCase to database fields
const UserSchema = new mongoose.Schema({
  firstName: { type: String, required: true },       // Database Fields:firstName
  emailAddress: { type: String, required: true },   // Database Fields:emailAddress
  createdAt: { type: Date, default: Date.now },     // Database Fields:createdAt
  isActive: { type: Boolean, default: true }        // Database Fields:isActive
});

// Through toJSON Convert Underscore-Based Naming Conventions(API On the way back)
UserSchema.set('toJSON', {
  virtuals: true,
  versionKey: false,
  transform: (doc, ret) => {
    ret.first_name = ret.firstName;
    delete ret.firstName;
    return ret;
  }
});

7. Document Size Limits

Concept Explanation: The maximum size for a single MongoDB document is 16 MB, and the maximum nesting depth is 100 levels. This limitation is a core design philosophy of MongoDB—it encourages embedding related data within a single document (to avoid JOINs), but discourages storing extremely large documents. The 16 MB limit allows MongoDB to process individual documents efficiently in memory, ensuring fast response times for queries and updates.

How It Works: The underlying reason for the 16 MB limit is that MongoDB’s WiredTiger storage engine uses an “in-place update” strategy when modifying documents—if the document becomes larger after the update and there is insufficient space at its original location, the document must be moved to a new location, which triggers an index update (all index entries pointing to that document must be updated). The larger the document, the higher the cost of moving it. Therefore, MongoDB has chosen 16 MB as a balance point.

Dimension Constraint Reason
Single Document Size 16 MB Maximum BSON Document Size
Nesting Depth 100 levels (default) Prevents stack overflow
Field Name Length 255 bytes UTF-8 encoding
Number of indexes 64 per collection Index metadata size
Total length of a single clustered index key 1024 bytes Index efficiency
Out-of-Bounds Scenarios Solutions Description
Large files (images/videos) GridFS Block storage, 255 KB per block
Very Long Text Elasticsearch + Citations Documents Store IDs, ES Stores Full Text
Array too large (comment list) Split into separate collections comments collection + reference
Excessively Deep Hierarchy Flat Design Reduce Hierarchy Levels

(1) 16 MB limit

(2) Why 16 MB?

MongoDB Design Philosophy: Avoid Storing Huge Documents:

(3) Solutions for Large-Document Scenarios

100%
graph TB
    A[Large-Document Scenarios] --> B[Binary file<br/>Image/Video]
    A --> C[Long Text<br/>Article/Log]
    A --> D[The array is too large<br/>List of Comments]

    B --> E[GridFS<br/>Block Storage]
    C --> F[Text Search<br/>Elasticsearch]
    D --> G[Split Set<br/>comments Gathering]

    style E fill:#d4edda
    style F fill:#d4edda
    style G fill:#d4edda

▶ Example 5: Storing Large Files in GridFS

JAVASCRIPT
// === Storing Large Files(>16MB)===
const mongoose = require('mongoose');
const Grid = require('gridfs-stream');
const fs = require('fs');

const conn = mongoose.connection;
let gfs;
conn.once('open', () => {
  gfs = Grid(conn.db, mongoose.mongo);
  gfs.collection('uploads');
});

// Upload File
const writestream = gfs.createWriteStream({
  filename: 'large-video.mp4',
  content_type: 'video/mp4'
});

fs.createReadStream('./local-video.mp4').pipe(writestream);

writestream.on('close', (file) => {
  console.log(`File stored: ${file._id}`);
});

// Download File
const readstream = gfs.createReadStream({
  _id: ObjectId('507f1f77bcf86cd799439011')
});

readstream.pipe(fs.createWriteStream('./downloaded-video.mp4'));

8. Embedded Documentation vs. Citations

Concept Explanation: There are two main strategies for modeling document relationships in MongoDB—embedded (Embed) and referenced (Reference). The embedded approach embeds related data directly within the parent document, allowing all data to be retrieved in a single query; the referenced approach stores related data in separate collections, accessed via ObjectId references, requiring multiple queries using $lookup or at the application layer. Choosing between these two strategies is the most critical decision in MongoDB data modeling.

How It Works: Embedded documents and parent documents are stored in the same BSON document and share the same lifecycle—when the parent document is updated, the embedded document is also overwritten, and when the parent document is queried, the embedded document is returned along with it. Referenced documents are independent BSON documents with their own _id and lifecycle; updates to one do not affect the other, but querying them requires an additional association operation.

100%
graph TB
    A[Document Relationship Modeling] --> B{Data Characteristics}
    B -->|1:1 Relationship<br/>Small data set<br/>We often read together| C[Embedded ✅<br/>Retrieve in a single query]
    B -->|1:N Relationship<br/>N Smaller<br/>It is rarely checked on its own.| D[Embedded ✅<br/>Nested Arrays]
    B -->|1:N Relationship<br/>N Larger<br/>Needs to be checked separately| E[Quotation Style ✅<br/>Independent Set]
    B -->|N:N Relationship| F[Quotation Style ✅<br/>Two-way ID Array]
    B -->|Frequent Updates to Subdocuments| G[Quotation Style ✅<br/>Avoid rewriting the entire document]

    style C fill:#d4edda
    style D fill:#d4edda
    style E fill:#d4edda
Scenario Recommendation Reason
1:1 Relationship (User-Address) Embedded (unless the address changes frequently) Retrieve all in a single query
1:N Relationship (User-Order) Depends on the value of N:
Small → Embedded; Large → Referenced
Document Size Limit
N:N Relationship (User-Role) Reference (two-way ID array) Complex relationship
Frequent Updates to Subdocuments Reference Avoid Rewriting the Entire Document
Requires separate queries for subdocuments Reference Performance of separate queries

(1) Two Strategies for Modeling Relationships

100%
graph TB
    subgraph "Embedded Documentation(Embed)"
        A1[users Gathering] --> A2[Document 1<br/>address: {<br/>  city: Tokyo<br/>  country: Japan<br/>}]
    end

    subgraph "Citation-Style Documentation(Reference)"
        B1[users Gathering] --> B2[Document 1<br/>address_id: ObjectId]
        B3[addresses Gathering] --> B4[Document 1<br/>city: Tokyo]
        B2 -.->|Search| B3
    end

(2) Select a Strategy

Scenario Recommendation Reason
1:1 Relationship (User-Address) Embedded (unless the address changes frequently) Retrieve all in a single query
1:N Relationship (User-Order) Depends on the value of N:
Small → Embedded; Large → Referenced
Document Size Limit
N:N Relationship (User-Role) Reference (two-way ID array) Complex relationship
Frequent Updates to Subdocuments Reference Avoid Rewriting the Entire Document
Requires separate queries for subdocuments Reference Performance of separate queries

(3) Example of an Embedded Document

JAVASCRIPT
// === Embedded:User + Multiple Addresses ===
db.users.insertOne({
  _id: ObjectId("507f1f77bcf86cd799439011"),
  name: "Alice",
  email: "alice@example.com",
  addresses: [                          // Nested Arrays
    {
      type: "home",
      street: "123 Main St",
      city: "Tokyo",
      country: "Japan",
      zip: "100-0001"
    },
    {
      type: "work",
      street: "456 Office Rd",
      city: "Tokyo",
      country: "Japan",
      zip: "100-0002"
    }
  ]
});

// === Search:Living in Tokyo users ===
db.users.find({ "addresses.city": "Tokyo" });

▶ Example 6: Example of a Citation-Style Document

JAVASCRIPT
// === Quotation Style:User + Order(Many-to-one) ===
// users Gathering
db.users.insertOne({
  _id: ObjectId("507f1f77bcf86cd799439011"),
  name: "Alice",
  email: "alice@example.com"
});

// orders Gathering
db.orders.insertMany([
  {
    _id: ObjectId("507f1f77bcf86cd799439012"),
    user_id: ObjectId("507f1f77bcf86cd799439011"),  // Quote
    items: ["PHONE-001", "CASE-002"],
    total: NumberDecimal("649.98"),
    createdAt: new Date()
  },
  {
    _id: ObjectId("507f1f77bcf86cd799439013"),
    user_id: ObjectId("507f1f77bcf86cd799439011"),  // Quote
    items: ["LAPTOP-001"],
    total: NumberDecimal("1299.99"),
    createdAt: new Date()
  }
]);

// === Usage $lookup Joined Queries(Similar SQL JOIN) ===
db.users.aggregate([
  { $match: { name: "Alice" } },
  { $lookup: {
      from: "orders",
      localField: "_id",
      foreignField: "user_id",
      as: "orders"
  }}
]);

9. Comprehensive Hands-On Exercise: Designing User Documentation for E-commerce

(1) Scenario Requirements

Design user documentation for an e-commerce platform. Requirements:

(2) Document Design

JAVASCRIPT
// === Comprehensive User Documentation ===
db.users.insertOne({
  _id: ObjectId("507f1f77bcf86cd799439011"),

  // === Basic Information ===
  email: "alice@example.com",
  username: "alice_chen",
  displayName: "Alice Chen",
  phone: "+81-90-1234-5678",

  // === Certification ===
  passwordHash: "$2b$10$...",      // bcrypt Hash(Not explicitly stated)
  emailVerified: true,
  twoFactorEnabled: false,

  // === Preferences(Nested Documents)===
  preferences: {
    language: "ja",
    currency: "JPY",
    timezone: "Asia/Tokyo",
    notifications: {
      email: true,
      sms: false,
      push: true,
      marketing: false
    }
  },

  // === Shipping Address(Nested Arrays)===
  addresses: [
    {
      addressId: ObjectId("..."),
      type: "home",
      isDefault: true,
      street: "1-2-3 Shibuya",
      city: "Tokyo",
      prefecture: "Tokyo",
      zip: "150-0002",
      country: "Japan",
      phone: "+81-90-1234-5678"
    }
  ],

  // === Statistics(It is recommended to split fields that are updated frequently)===
  stats: {
    totalOrders: 25,
    totalSpent: NumberDecimal("125430.50"),
    averageRating: 4.7,
    lastOrderAt: ISODate("2026-06-15T10:30:00Z")
  },

  // === Watchlist(Quotation Style)===
  followingIds: [
    ObjectId("507f1f77bcf86cd799439012"),
    ObjectId("507f1f77bcf86cd799439013")
  ],

  // === Profile Picture Citation(GridFS)===
  avatarFileId: ObjectId("507f1f77bcf86cd799439099"),

  // === Metadata ===
  createdAt: ISODate("2025-03-01T10:00:00Z"),
  updatedAt: ISODate("2026-07-01T15:23:00Z"),
  lastLoginAt: ISODate("2026-07-01T10:00:00Z"),
  isActive: true,
  role: "customer"  // customer | admin | moderator
});

(3) Mongoose Schema Mapping

JAVASCRIPT
const UserSchema = new mongoose.Schema({
  email: { type: String, required: true, unique: true, lowercase: true },
  username: { type: String, required: true, unique: true, index: true },
  displayName: { type: String, required: true },
  phone: { type: String },

  passwordHash: { type: String, required: true, select: false },
  emailVerified: { type: Boolean, default: false },
  twoFactorEnabled: { type: Boolean, default: false },

  preferences: {
    language: { type: String, default: 'en' },
    currency: { type: String, default: 'USD' },
    timezone: { type: String, default: 'UTC' },
    notifications: {
      email: { type: Boolean, default: true },
      sms: { type: Boolean, default: false },
      push: { type: Boolean, default: true },
      marketing: { type: Boolean, default: false }
    }
  },

  addresses: [{
    addressId: { type: mongoose.Schema.Types.ObjectId, default: () => new mongoose.Types.ObjectId() },
    type: { type: String, enum: ['home', 'work', 'other'], default: 'home' },
    isDefault: { type: Boolean, default: false },
    street: { type: String, required: true },
    city: { type: String, required: true },
    prefecture: String,
    zip: { type: String, required: true },
    country: { type: String, required: true },
    phone: String
  }],

  stats: {
    totalOrders: { type: Number, default: 0 },
    totalSpent: { type: mongoose.Schema.Types.Decimal128, default: 0 },
    averageRating: { type: Number, default: 0 },
    lastOrderAt: Date
  },

  followingIds: [{ type: mongoose.Schema.Types.ObjectId, ref: 'User' }],
  avatarFileId: { type: mongoose.Schema.Types.ObjectId },

  role: { type: String, enum: ['customer', 'admin', 'moderator'], default: 'customer', index: true },
  isActive: { type: Boolean, default: true, index: true }
}, { timestamps: true });

❓ FAQ

Q Why does MongoDB use BSON instead of storing JSON directly?
A JSON is a text format that is slow to parse, has limited data types, and does not guarantee field order. BSON is a binary format that is fast to parse (in milliseconds), supports a rich set of data types (such as Date, Binary, and Decimal128), and preserves field order (important!), making it better suited for database storage and querying.
Q Is ObjectId truly unique?
A In theory, the same counter within the same process will not be duplicated within the same second. In practice, collisions are virtually impossible (a 5-byte random value = 2^40 ≈ 1 trillion possibilities). If absolute uniqueness is required (e.g., in finance), you can manually specify _id: ObjectId() or a UUID.
Q Are field names case-sensitive?
A Yes. firstName and FirstName are different fields. MongoDB is strictly case-sensitive. We recommend using a consistent naming convention throughout (camelCase is recommended).
Q Can the _id field be modified?
A Yes, but it is not recommended. _id is the document’s unique identifier, and modifying it will break the reference relationships. If you need a business primary key (such as an order number), you can use a custom primary key like _id: "ORDER-2026-07-001", but this will result in slower query performance.
Q How do I choose between Decimal128 and Double?
A Use Decimal128 for finance, monetary amounts, and precise calculations. Use Double (which is faster) for scientific computing, statistics, and graphics rendering. Using Decimal128 incorrectly can lead to the classic 0.1 + 0.2 = 0.30000000000000004 problem.
Q Is query performance good for embedded documents?
A Yes. Once MongoDB indexes embedded documents, query performance is comparable to that of standalone collections. However, please note: (1) The total document size cannot exceed 16 MB; (2) Multikey indexes on array fields have size limitations.
Q Can field names be in Chinese?
A Yes, but it’s not recommended. For example, { name: "Alice" } is valid, but it offers poor support for debugging, logging, and third-party tools. We recommend using English throughout.

📖 Summary


📝 Exercises

  1. Basic Question (⭐): Insert a product document into Mongosh (containing 6 or more fields: String, Number, Date, Array, Object, Boolean), then use findOne() to query and verify the field types.

  2. Basic Exercise (⭐): Write a Node.js script to create a user schema using Mongoose (including a balance field of type Decimal128 and an avatar field of type Buffer), insert data, and print the timestamp of the ObjectId.

  3. Advanced Exercise (⭐⭐): Design a document structure for a blog post (with 5 or more fields), use insertMany to insert 5 posts, and demonstrate the design of an embedded comments array.

  4. Advanced Problem (⭐⭐): Write a script to extract the timestamps from the ObjectId fields of 100 documents, group them by date, and count the number of documents for each day.

  5. Advanced Problem (⭐⭐): Compare the query performance of embedded and referenced documents: Using 1 million records, store the “user-order” relationship using both embedded and referenced storage methods, and measure response times using $lookup and nested queries.

  6. Challenge (⭐⭐⭐): Use GridFS to implement a file upload/download API that supports uploading files up to 100MB in size, verifies the chunked storage mechanism, and implements download progress tracking.

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏