134: MongoDB Fundamentals — Documents, BSON, Atlas, Data Types, and When to Use MongoDB
Learning objectives
By the end of this lesson, you should be able to:
- explain what MongoDB is;
- distinguish document databases from relational databases;
- understand database, collection, document, field, and
_id; - understand BSON versus JSON;
- use common BSON data types correctly;
- understand
ObjectId; - install/connect to MongoDB locally or through Atlas;
- use
mongosh; - create collections and inspect documents;
- understand flexible schema versus schema-less misconception;
- decide when MongoDB is a good or poor fit;
- understand MongoDB 8.3 as the current stable release line in 2026.
Baseline
As of August 2026, MongoDB 8.3 is the current stable release line. The exact version running on your machine or cluster may differ, so treat the installed version as something to inspect rather than assume.
Production deployments should run supported, patched versions and follow MongoDB's versioning/upgrade guidance. Development commands in this lesson are not a substitute for production configuration or upgrade planning.
Check the server version from mongosh with:
db.version()
That gives you a concrete baseline before you troubleshoot behavior or compare it with current documentation.
What MongoDB is
MongoDB is a document-oriented database. The useful distinction is not simply “SQL versus NoSQL”; it is how the database represents and retrieves related data.
Instead of relational rows spread across tables such as:
users table
orders table
order_items table
MongoDB stores BSON documents in collections. A document can contain nested documents and arrays, so a related aggregate can often be read as one value:
{
_id: ObjectId("..."),
customerId: ObjectId("..."),
status: "pending",
items: [
{
productId: ObjectId("..."),
name: "Notebook",
quantity: 2,
pricePaise: 12000
}
],
totals: {
subtotalPaise: 24000,
taxPaise: 4320,
totalPaise: 28320
},
createdAt: ISODate("2026-08-27T10:00:00Z")
}
Here, the order, its line items, totals, and creation time are represented together. Related data can be embedded directly inside one document, which is especially useful when the application normally reads or writes that data together.
That does not mean every related record belongs in one document. Embedding is a modeling choice driven by access patterns, update behavior, and document-size limits. Those trade-offs become more important as the data model grows.
Core terminology
MongoDB has several layers and execution concepts. Keep the following model in mind while using the shell:
server / deployment
database
collection
document
field
index
query
cursor
aggregation pipeline
replica set
sharded cluster
A deployment contains databases. A database contains collections, and a collection contains documents. Fields are the named values inside documents. Indexes support particular query patterns; a query describes what to retrieve; a cursor represents the result stream; and an aggregation pipeline transforms or summarizes documents. Replica sets and sharded clusters describe deployment topologies rather than document fields.
For example:
database: commerce
collection: orders
document: one order
field: status
The distinctions matter when debugging. If a collection exists but a query is slow, the problem may involve the query shape or its indexes, not the database name. If a deployment fails over, that is a replica-set concern, not a document-schema concern.
BSON versus JSON
Application code often displays MongoDB documents in a JSON-like form, but MongoDB stores and transmits BSON. JSON supports a relatively small type set:
string
number
boolean
null
array
object
BSON is a binary serialization format used by MongoDB and supports more data types. That distinction is why a value that looks like a JavaScript number or string still needs to be stored with the intended database type.
Important BSON types include:
String
Double
Int32
Int64/Long
Decimal128
Boolean
Date
ObjectId
Array
Embedded Document
Binary
Regular Expression
Timestamp
Null
MinKey
MaxKey
Some historical or deprecated BSON types may still appear in old data or documentation. Do not choose obsolete types for new schemas. When a driver displays a BSON value using helper syntax, that display is also a clue that the value is not merely an ordinary JSON primitive.
Why BSON types matter
These two documents look similar when printed, but their amount fields have different BSON types:
{ amount: 100 }
and:
{ amount: "100" }
A query such as:
db.orders.find({
amount: 100
})
does not mean “coerce every string 100.” It asks for the numeric value represented by 100; it does not automatically turn the string value into a number.
Type consistency affects:
- queries;
- sorting;
- indexes;
- aggregation;
- validation.
When a query unexpectedly returns no documents, inspect both the field name and the stored type before changing the query. Flexible schema does not mean type discipline is irrelevant. A collection can allow different document shapes while still needing a consistent type for a field that is queried, sorted, or indexed.
_id
Every document has a unique _id field. If you omit it, drivers commonly generate an ObjectId before insertion. The field is the document's identity within its collection:
{
_id: ObjectId("66d0...")
}
You can use another unique type or value for _id, but choose deliberately. Its type becomes part of how callers and queries address the document, so changing conventions later has a real migration cost.
Do not change _id after insertion. If an application needs a different identifier for display or an external system, store that as a separate field with the appropriate uniqueness and validation rules.
ObjectId
ObjectId is a 12-byte BSON identifier type. It is commonly generated client-side by drivers and includes a timestamp component, which makes values roughly time-sortable. That ordering is approximate, not a replacement for an explicit event or creation date.
An ObjectId is:
- compact;
- generated client-side by drivers;
- roughly time-sortable due to timestamp component;
- not a secret;
- not authorization.
This is where people usually get confused: an identifier can be difficult to guess in some situations without being an access-control mechanism. Never assume:
unpredictable ObjectId = secure access control
A user who learns another tenant's ID still must be blocked by server authorization and query scoping. The server should establish which tenant the authenticated user may access and include that boundary in the query; it must not rely on the shape of _id to enforce the boundary.
ObjectId conversion
In the shell, construct an ObjectId explicitly:
ObjectId("66d0...")
In the Node driver, use its BSON helper:
import { ObjectId } from 'mongodb';
const id = new ObjectId(rawId);
Validate the string format before using it. Invalid input should produce a controlled client error, not an unhandled conversion exception or a query built from unchecked input.
Do not query:
{ _id: req.params.id }
when _id is an ObjectId. req.params.id is normally a string, and MongoDB does not treat that string as the same value as an ObjectId. The type mismatch returns nothing. Convert the validated route parameter to the type used by the collection, and apply authorization or tenant scoping in the same server-side query.
Dates
Store an actual BSON Date when a value represents a point in time:
{
createdAt: new Date()
}
Do not store a presentation-formatted string such as:
{
createdAt: "27/08/2026"
}
A BSON Date supports meaningful comparison and indexing. A formatted string carries presentation choices, such as day-first ordering, into storage and can produce incorrect sorting or ambiguous parsing. The application can format a real date for a user's locale at presentation time.
Money
Do not casually use floating-point Double for exact financial arithmetic. Binary floating point cannot represent every decimal fraction exactly, so repeated calculations and comparisons can produce values that are unsuitable for currency.
Two common storage strategies are integer minor units and Decimal128.
Integer minor units
Store the smallest relevant currency unit, such as paise, as an integer:
{
amountPaise: NumberLong("12500")
}
For smaller ranges, a safe integer within JavaScript and driver limitations may also be sufficient. The domain must define the unit and keep that convention consistent.
Decimal128
Store a decimal value using BSON Decimal128:
{
amount: Decimal128("125.00")
}
Use a consistent domain strategy. Do not let one writer store minor units while another stores a floating-point amount in the same field. Also understand the driver's conversion behavior: JavaScript Number cannot exactly represent every Int64 or decimal value. Decide where conversion occurs and whether the application should use strings, driver BSON helper types, or a decimal library at its boundaries.
Integers
BSON distinguishes Int32, Int64, and Double. JavaScript's ordinary number is an IEEE-754 double, so it does not provide exact representation for every possible 64-bit integer.
The MongoDB Node driver provides BSON helper types for exact 64-bit and decimal values. Do not silently convert a huge Int64 to a JavaScript number if it exceeds JavaScript's safe integer range. The following check matters when values cross from BSON into ordinary JavaScript arithmetic:
Number.isSafeInteger(...)
If a value is outside that range, preserve it with an appropriate driver type or deliberate representation instead of accepting silent precision loss.
Binary
BSON Binary is useful for values such as:
- hashes;
- UUID encodings;
- encrypted values;
- small binary metadata.
For large files, MongoDB GridFS is a specialized mechanism, but object storage is often a better architecture for application files. A normal document should not become a file store simply because it can technically contain binary data.
Advanced lesson covers GridFS.
Arrays
Arrays are first-class document values. For example:
{
tags: ["node", "mongodb"],
items: [
{ sku: "A", quantity: 2 },
{ sku: "B", quantity: 1 }
]
}
MongoDB can query and index array fields, including fields inside array elements. That convenience does not remove the need to think about growth. Arrays can grow a document dramatically; an unbounded array is a common modeling mistake because each later append makes the same parent document larger and potentially more expensive to update or read.
Embedded documents
Nested objects are embedded documents. A shipping address might be stored as:
{
shippingAddress: {
line1: "...",
city: "...",
postalCode: "..."
}
}
Embedding is a core MongoDB modeling tool. It keeps data that is usually consumed together close to the document that owns it and can allow a single-document atomic write.
But “embed everything” is not the rule. The right choice depends on access patterns and document growth. If a related value is independently queried, updated at a different rate, or can grow without a useful bound, referencing or separating it may be more appropriate.
Lesson 135 covers modeling deeply.
MongoDB document limits
MongoDB documents have a maximum BSON document size. The limit is a platform constraint, not merely a performance guideline.
Do not design a document like this:
one user document
→ array of every event forever
Unbounded growth will eventually fail or become inefficient. A growing event history may need separate documents, a bounded recent-events array, or another storage design. Know platform limits from current docs, and evaluate the expected growth before choosing an embedded array.
Flexible schema
MongoDB permits documents in one collection to have different shapes. For example, a polymorphic collection could contain:
{ type: "email", email: "a@example.com" }
{ type: "phone", phone: "+..." }
This is useful when related records genuinely have different fields. It does not mean that every document may be arbitrary or that consumers should guess which fields exist.
Production collections still need intentional contracts. MongoDB supports collection schema validation, and applications can also validate using driver, ODM, or schema libraries. Decide which fields are required, which types are allowed, and how older document versions are handled.
Create database/collection
MongoDB creates a database and collection lazily in common workflows when data is first written. Explicit creation is still useful when you need validators or collection options, because it makes those choices visible before inserts occur.
In mongosh:
use course
db.createCollection("tasks")
Insert a document:
db.tasks.insertOne({
title: "Learn MongoDB",
completed: false,
createdAt: new Date()
})
The driver or shell reports an inserted identifier. That identifier is the _id value used to address the document later.
Find
To inspect documents in the current collection:
db.tasks.find()
A focused query is easier to read and is closer to what application code normally needs:
db.tasks.find({
completed: false
})
Do not confuse find() returning a cursor with returning one in-memory array. A cursor represents a result set that can be consumed, limited, projected, or iterated. This distinction matters for memory use and for understanding when data is actually fetched.
Cursors are covered in CRUD lesson.
Atlas
MongoDB Atlas is MongoDB's managed cloud service. It can provide:
- managed clusters;
- backups;
- monitoring;
- search/vector features;
- network/security controls.
These managed capabilities reduce operational work, but they do not make an unsafe network policy safe. Do not expose Atlas to 0.0.0.0/0 with weak credentials in production just for convenience. Use network access rules and private networking according to the architecture, and apply least privilege to database users and application credentials.
Local Community Server
Local development options include:
- native install;
- Docker/container;
- local Atlas-related tooling where available.
The choice affects setup and lifecycle, not the need for sound data modeling. Keep development data separate from production. Do not point local experiments at a production cluster or reuse production credentials in a development environment.
Connection string
A local connection string may look like this:
mongodb://localhost:27017/course
Atlas commonly uses the SRV form:
mongodb+srv://...
A connection string can include credentials. Never commit it if it is secret-bearing. Use environment configuration or a secret manager, restrict who can read the secret, and avoid logging the full value when diagnosing connection failures.
mongosh
mongosh is useful for learning, administration, and focused diagnostics. These commands inspect the current environment and collection:
show dbs
show collections
db.getName()
db.tasks.findOne()
db.tasks.countDocuments()
Use the shell for learning and admin diagnostics. Production application code uses a driver so it can manage connection lifecycles, errors, timeouts, validation, and application-level authorization deliberately.
MongoDB versus PostgreSQL
Choose MongoDB when the data and its access patterns fit a document model. Good candidates can include:
- content/catalog;
- nested aggregates;
- event/config documents;
- evolving polymorphic data;
- workloads benefiting from native sharding.
PostgreSQL may be a better fit when the dominant requirements are:
- complex relational integrity;
- join-heavy normalized model;
- strict cross-entity transactions;
- complex SQL analytics;
- relational constraints dominate.
MongoDB supports transactions and joins through $lookup, so the comparison is not “MongoDB cannot do either.” The practical question is which model makes the common operations, constraints, and queries natural. If every operation depends on many cross-document transactions or joins, a relational model may be more natural and easier to maintain.
Atomicity
MongoDB single-document write operations are atomic at the document level. A write to one document does not expose a partially updated version of that document.
This is one reason that embedding data which changes together can reduce the need for multi-document transactions. It is not a blanket instruction to embed. Document size, read patterns, independent updates, and growth still determine whether embedding is a good model.
Do not choose embedding solely for atomicity; document size and access patterns matter too.
Naming
Collections are commonly plural:
users
orders
tasks
Field names should be:
- consistent;
- predictable;
- free of unnecessary deeply nested paths;
- free of dynamic user-controlled field names where possible.
MongoDB permits many characters and structures, but not every permitted schema is maintainable. Stable names make queries, indexes, validation, analytics, and application code easier to reason about.
Schema validation preview
Database-level validation can enforce a basic contract even when more than one application or tool writes to the collection:
db.createCollection("tasks", {
validator: {
$jsonSchema: {
bsonType: "object",
required: [
"title",
"completed"
],
properties: {
title: {
bsonType: "string",
minLength: 3
},
completed: {
bsonType: "bool"
}
}
}
}
})
Database-level validation complements application validation. Do not rely only on Mongoose if other writers can bypass it. Conversely, database validation does not replace application-level rules such as authorization, business workflows, or user-friendly error messages.
Common mistakes
The mistakes below are worth recognizing during design and debugging:
- “NoSQL means no schema”;
- strings for dates/numbers;
- ObjectId treated as permission;
- unbounded arrays;
- huge files embedded in normal documents;
- Mongo chosen because JavaScript uses JSON;
- connection string committed;
- Atlas open to internet broadly;
- float for exact money without strategy;
- different types in same indexed field unintentionally.
Most of these failures come from treating a convenient syntax or flexible feature as a design decision. Inspect actual stored types, expected access patterns, growth, network boundaries, and writer behavior instead.
Exercises
Work through these in order. The early exercises establish the basic objects and types; the later ones require you to reason about modeling, security, and database choice.
- Install/connect to MongoDB.
- Create tasks collection.
- Insert documents with Date/ObjectId.
- Compare string date versus Date queries.
- Store exact currency with two alternative strategies.
- Inspect BSON types.
- Add JSON schema validator.
- Model one SQL-like entity as a document and identify trade-offs.
- Decide Mongo vs PostgreSQL for five scenarios.
- Explain why ObjectId does not provide authorization.
Mastery checklist
Before moving on, make sure you can explain:
- MongoDB/document database;
- BSON/JSON;
- common BSON types;
- ObjectId;
- dates/money;
- collections/documents;
- flexible schema;
- Atlas/local;
- atomic document writes;
- use-case fit;
- document size/unbounded-array risks.
These are practical checks, not just vocabulary. You should be able to recognize the types in a document, choose a safe representation, inspect a collection with mongosh, and explain why a particular workload fits or does not fit MongoDB.
Official references
Use the current MongoDB documentation for version-specific behavior and platform limits:
