How MongoDB stores data as documents, how to query, update, aggregate and index them in mongosh, and where it fits compared with a relational database.
MongoDB is a document database. Instead of rows in tables, it stores records as JSON-like documents grouped into collections, and a document can contain nested objects and arrays. You query it with a JSON-style query language, not SQL.
A record looks like this in mongosh, the MongoDB shell:
A server selection timeout means the driver can't reach the cluster; a pool timeout means you've run out of connections. How to diagnose each, including the serverless one-client-per-request trap.
Nothing was created beforehand. The store database and the products collection come into existence on the first insert, and the third document has a field the others lack. That flexibility is the main attraction of MongoDB and, used carelessly, its main hazard.
How MongoDB terms map to relational ones
Relational database
MongoDB
Database
Database
Table
Collection
Row
Document
Column
Field
Primary key
The _id field
Index
Index
JOIN
Embedded documents, or the $lookup aggregation stage
GROUP BY
The $group aggregation stage
Foreign key constraint
None. References are by convention; the application keeps them valid
Documents and BSON
Documents are stored in BSON, a binary encoding of JSON with more data types: dates, 32- and 64-bit integers, 128-bit decimals, binary data and ObjectId, among others. Types matter in queries: the string "42" does not match the number 42.
Rules worth knowing from the start:
Every document has an _id field that is unique within its collection. If you do not supply one, the driver generates an ObjectId.
A single document can be at most 16 MB.
Field names are case-sensitive. price and Price are different fields.
Embed or reference
In a relational design, a product's stock levels would be rows in another table. Here they are embedded in the product document, so one read returns everything. Embedding suits data that belongs to its parent and is read with it. Use a reference (store the other document's _id) when the related data is large, shared by many parents or grows without bound; an array that keeps growing will eventually hit the document size limit and makes every update more expensive.
Model around how the application reads and writes the data. That is the opposite of the relational habit of normalising first.
Reading and changing data
find takes a filter and, optionally, a projection listing the fields to return:
Conditions in a filter are combined with AND. tags: "peripherals" matches documents whose tags array contains that value. Nested fields use dot notation, as in { "stock.retail": { $lte: 5 } }.
Updates use operators that describe the change. Without an operator such as $set, you would be supplying a replacement document, which updateOne rejects:
Always check matchedCount. A filter with a misspelt field name is not an error; it matches nothing and the update silently does nothing.
deleteOne and deleteMany take the same kind of filter. deleteMany({}) removes every document in the collection, so write the filter first and test it with find.
A write to a single document is atomic, including changes to its embedded fields and arrays.
Aggregation
For anything beyond simple filtering, MongoDB uses an aggregation pipeline: a list of stages, each transforming the output of the one before. This counts products and averages the price per tag:
$match filters, $group aggregates, $project reshapes, $sort and $limit do what their names say, and $lookup performs a left outer join to another collection.
Indexes
Without an index, a query scans every document in the collection. MongoDB creates an index on _id automatically; you add the others:
In the explain output, a winning plan containing IXSCAN used an index and COLLSCAN scanned the whole collection. Compare totalDocsExamined with the number of documents returned to see how much work a query did. Indexes can cover several fields, array elements, text and geospatial data. Each one speeds up reads and adds cost to writes.
Schema validation
"Schemaless" means MongoDB does not require a schema, not that you should go without one. A collection can carry a validator, written as JSON Schema, that rejects documents which do not conform:
After this, inserting a product without a price fails with MongoServerError: Document failed validation. For money, prefer the decimal type (Decimal128("89.00") in mongosh) over the default floating-point number.
Replication, sharding and transactions
Replica sets keep copies of the data on several servers. One member, the primary, accepts writes; the others replicate from it, and one is elected primary automatically if the current primary fails. Production deployments run as a replica set.
Sharding splits a large collection across several replica sets according to a shard key, so data size and write load can grow beyond one server.
Multi-document transactions with ACID guarantees are supported on replica sets (since MongoDB 4.0) and sharded clusters (since 4.2). They cost more than single-document writes, and a design that needs them constantly is often better served by embedding or by a relational database.
Editions and licence
Offering
What it is
MongoDB Community Edition
The free, self-managed server
MongoDB Enterprise Advanced
A commercial subscription that adds features such as Kerberos authentication, auditing, encryption at rest and an in-memory storage engine
MongoDB Atlas
MongoDB's managed cloud service, which includes a free tier
Since October 2018 the server has been published under the Server Side Public License (SSPL). The source code is available and the Community Edition is free to use, but the SSPL is not approved by the Open Source Initiative, so MongoDB is "source-available", not open source in that sense. For most users this changes nothing. It matters if you plan to offer MongoDB itself as a hosted service to others. The official drivers are under the Apache License 2.0.
Official drivers exist for the common languages, including JavaScript, Python, Java, C#, Go, PHP, Ruby, Rust, C and C++.
When MongoDB fits and when it does not
It is a reasonable choice when:
Records are naturally hierarchical or vary in shape: product catalogues, content, user profiles, event data.
You read and write whole objects more often than you join across them.
The structure is still changing, or you expect to need horizontal scaling.
Look at a relational database instead when the data is highly interconnected, when you rely on joins and constraints across many entities, or when ad hoc reporting across everything is the main workload.
Common mistakes
No indexes. Queries work on a small collection and slow down as it grows. Check explain() for COLLSCAN.
Treating flexibility as "no design needed". Inconsistent field names and types accumulate. Add validation early.
Unbounded arrays. Embedding every comment or log entry in one parent document.
Mixing types. Storing a price as a string in some documents and a number in others; range queries and sorts then misbehave.
Exposing an unsecured server. A self-managed installation has access control turned off until you enable it. Do that before the server listens on anything but localhost.
Using the old shell. Tutorials that start with the mongo command predate its replacement by mongosh.
Twelve common MongoDB relationships, from user addresses to org charts, each with an embed, reference or hybrid verdict, the document shape and the reason.
A complete schema for a multi-tenant project tracker: collections, types, ESR indexes checked with explain(), validation, migrations, write concerns and sharding readiness.