MongoDB Introduction
MongoDB is a document database: instead of storing information in tables made of rows and columns, it stores each record as a self-contained document written in a JSON-like format called BSON. It is the most widely used NoSQL database, built to handle data that does not fit neatly into a fixed table schema — nested objects, arrays of variable length, and records whose shape changes over time. This lesson introduces what MongoDB is, why it exists, how its core pieces fit together, and gives you a first hands-on look at the mongosh shell before later lessons in this section go deeper on installation, the document model, and BSON.
Overview: What Is MongoDB and How Does It Work?
MongoDB is a document database. Where a relational (SQL) database stores data as rows in rigidly-structured tables, MongoDB stores each record as a single self-contained document — a set of field/value pairs that looks a lot like a JavaScript object. A collection of related documents (roughly analogous to a SQL table) can hold documents that vary in shape: one users document might have a phone field and another might not, with no ALTER TABLE statement involved. This is called schema flexibility, and it is MongoDB’s central design idea.
Under the hood, documents are not stored as plain JSON text. They are stored as BSON (Binary JSON), a binary-encoded format that is fast to parse and adds data types plain JSON does not have on its own — a true Date type, 64-bit integers, a Decimal128 type for exact decimal math (useful for money), binary blobs, and MongoDB’s own ObjectId type used for the default _id primary key. You will meet BSON’s types in detail in a later lesson; for now, just know that every document you insert is converted to BSON before it is written to disk, and converted back to a JS-friendly object when mongosh prints it to you.
A running MongoDB server is a process called mongod. It listens for connections, manages one or more databases, and persists data to disk through a pluggable storage engine (WiredTiger by default, offering document-level locking and compression). You talk to mongod through a client — most commonly mongosh, the modern MongoDB shell, which is itself a full JavaScript REPL: every command you type, aside from a handful of shell-only shortcuts like use and show dbs, is real JavaScript executed against a MongoDB API.
Beyond a single server, MongoDB scales in two ways you will meet later in this course: a replica set keeps multiple copies of the same data on different servers for high availability (if the primary goes down, the remaining members elect a new one), and sharding splits a very large collection across multiple servers once a single replica set can no longer hold or serve it efficiently. Neither is something a beginner needs to set up — a single-server deployment (or a free Atlas cluster, which is itself a small replica set) is plenty to learn on.
Schema flexibility does not mean "no design." An application still expects a users document to reliably have an email field of type string; MongoDB just lets you enforce that with optional schema validation rules rather than a rigid table definition, and lets you evolve the shape of your documents over time without a blocking migration. You get to choose how strict to be, collection by collection.
Syntax
Almost everything you do in MongoDB follows the same basic shape: pick a database, pick a collection inside it, then call a method on that collection.
db.collectionName.insertOne(document);
db.collectionName.insertMany([document1, document2]);
db.collectionName.find(query, projection);
db.collectionName.updateOne(filter, update);
db.collectionName.deleteOne(filter);
| Part | Meaning |
|---|---|
db |
The currently selected database (chosen with use, covered in the next lessons). |
collectionName |
The collection (like a SQL table) you are operating on. It does not need to be created in advance — writing to it creates it. |
document |
A plain JavaScript object literal, e.g. { name: "Ada", age: 30 }. |
query |
A filter object describing which documents to match, e.g. { age: { $gt: 25 } }. |
projection |
An optional object choosing which fields to return, e.g. { name: 1, _id: 0 }. |
You will reuse this exact collection-then-method pattern for every operation in this course, whether it is a single find, a multi-stage aggregation pipeline, or an index creation call.
Examples
Example 1: Insert your first document. Below, use bookstore is a mongosh-only shortcut (not JavaScript) that switches your session to a database named bookstore; the database is actually created the first time something is written to it.
use bookstore
db.books.insertOne({
title: "Dune",
author: "Frank Herbert",
year: 1965,
genres: ["sci-fi", "adventure"]
});
Output:
{
acknowledged: true,
insertedId: ObjectId("64f1a2b3c4d5e6f7a8b9c0d1")
}
MongoDB generated the _id field automatically because we did not supply one, using an ObjectId — a 12-byte value that embeds a timestamp, meaning ObjectIds are roughly sortable by creation time. The books collection did not exist before this call; it was created implicitly by the first write.
Example 2: Query the document you just inserted with a filter.
db.books.find({ year: { $gt: 1950 } });
Output:
[
{
_id: ObjectId("64f1a2b3c4d5e6f7a8b9c0d1"),
title: "Dune",
author: "Frank Herbert",
year: 1965,
genres: [ "sci-fi", "adventure" ]
}
]
$gt is a query operator meaning "greater than." find() always returns a cursor over an array of matching documents, even when only one document matches.
Example 3: A more realistic case showing schema flexibility in action. Two orders are inserted together with insertMany(); notice the second document has a giftWrap field the first one does not — both are perfectly valid in the same collection.
db.orders.insertMany([
{ customer: "Amit Shah", items: [{ sku: "B001", qty: 2 }], total: 45.50, status: "shipped" },
{ customer: "Priya Nair", items: [{ sku: "B002", qty: 1 }], total: 19.99, status: "pending", giftWrap: true }
]);
Output:
{
acknowledged: true,
insertedIds: {
'0': ObjectId("64f1a2c1c4d5e6f7a8b9c0d2"),
'1': ObjectId("64f1a2c1c4d5e6f7a8b9c0d3")
}
}
Now query only the pending order, returning just the fields we need with a projection:
db.orders.find({ status: "pending" }, { customer: 1, total: 1, _id: 0 });
Output:
[ { customer: "Priya Nair", total: 19.99 } ]
The items array is embedded directly inside each order document rather than stored in a separate collection, because line items belong to exactly one order and are always read together with it — a pattern the embedding-vs-referencing lessons later in this course cover in depth.
How It Works Step by Step
When you run insertOne(), mongosh does not send JSON text over the wire — it encodes your JavaScript object into BSON on the client side. The driver checks whether you supplied an _id; if not, it generates an ObjectId client-side before sending the request, so your application already knows the new document’s id before the server responds. The mongod process receives the BSON document and hands it to the WiredTiger storage engine, which writes it to its on-disk data structures and, by default, waits for acknowledgment from the storage layer before mongosh reports acknowledged: true. This default write behavior (the write concern) already guarantees the write is durable on a single node before your program moves on.
When you run find(), the query planner looks at the filter and decides how to satisfy it. With no index defined on books beyond the automatic index on _id, a filter like { year: { $gt: 1950 } } is satisfied with a collection scan (COLLSCAN): MongoDB reads every document in the collection and tests each one against the filter. For the tiny collections in these examples that is instant; on a collection with millions of documents it would be slow. Creating an index on year lets the planner instead perform an index scan (IXSCAN), jumping straight to the relevant range — the dedicated indexing lessons later in this course show you how to create one and how to read explain() output to confirm which strategy MongoDB actually chose.
Common Mistakes
Mistake 1: Treating "schema-less" as "no design needed." Because MongoDB does not reject a document for having an unexpected shape, it is easy to let field names drift, which silently breaks queries:
// Document A, inserted last month
db.orders.insertOne({ customer: "Amit Shah", Total: 45.50, status: "shipped" });
// Document B, inserted today by different code with a typo/casing mismatch
db.orders.insertOne({ customer: "Priya Nair", total: 19.99, status: "pending" });
// This query silently misses Document A because the field is "Total", not "total"
db.orders.find({ total: { $gt: 0 } });
MongoDB never warns you about this — both documents are perfectly valid on their own. The fix is naming discipline plus, ideally, a JSON Schema validator attached to the collection so a typo like Total is rejected at write time instead of silently corrupting later query results:
db.orders.insertOne({ customer: "Amit Shah", total: 45.50, status: "shipped" });
Mistake 2: Comparing an _id to a plain string. A very common bug happens when an id arrives as a string (for example, from a URL parameter in a web app) and is compared directly to the stored ObjectId:
// Wrong: idFromUrl is a string, never strictly equals a stored ObjectId
const idFromUrl = "64f1a2b3c4d5e6f7a8b9c0d1";
db.books.find({ _id: idFromUrl }); // matches nothing
// Correct: convert the string to an ObjectId first
const idFromUrl = "64f1a2b3c4d5e6f7a8b9c0d1";
db.books.find({ _id: new ObjectId(idFromUrl) });
An ObjectId and its 24-character hex string representation print identically, but they are different BSON types, so a strict equality match against the raw string never succeeds.
Best Practices
- Keep field names and types consistent across documents in the same collection, even though MongoDB does not force you to — treat schema flexibility as flexible evolution, not as no schema at all.
- Use a JSON Schema validator on collections where data integrity matters (for example
usersororders) so obviously malformed documents are rejected at write time. - Let MongoDB generate
_idas anObjectIdunless you have a specific reason to supply your own id. - Embed data that is always read together and bounded in size (an order’s line items); reference data that is large, shared across documents, or grows without bound (a full product catalog).
- Check
explain()on any query running against a collection that will grow large, so you catch a full collection scan before it becomes a production performance problem. - Start on a single MongoDB Atlas free-tier cluster or a local
mongod; only reach for sharding once a single replica set genuinely cannot keep up.
Practice Exercises
- Switch to a new database named
library, then insert three documents into amemberscollection. Give each member aname, ajoinedyear, and an array ofbooksBorrowed— but make the arrays different lengths for different members, to see schema flexibility in action. - Write a query against your
memberscollection that finds every member who joined after 2020 and returns only theirnamefield (hide_idin the projection). Expected result shape: an array of small objects, each just{ name: "..." }. - Insert one more member document but deliberately misspell one field (for example
Nameinstead ofname). Run your Exercise 2 query again and confirm the misspelled document is silently skipped — this is the schema-drift mistake from this lesson, reproduced on purpose so you recognize it later.
Summary
- MongoDB is a document database: each record is a self-contained BSON document rather than a row in a rigid table.
- Documents in the same collection can have different fields — schema flexibility is a feature, but it still requires application-level discipline or validation.
mongoshis a real JavaScript shell; only a few shortcuts such asuseandshow dbsare not JavaScript.- Every document gets a unique
_id, generated as anObjectIdif you do not supply one — never compare it to a plain string without converting first. - A single server (
mongod) is enough to learn on; replica sets and sharding solve availability and scale problems you will meet later in this course.
