#What MongoDB is
AddedMongoDB is a document database. Instead of splitting a customer across a customers table, an orders table and an order_items table and stitching them back together with joins, you can store the customer as one JSON-like document that already contains their address and their orders. Documents live in collections (the rough equivalent of tables), and collections live in databases. There is no CREATE TABLE: a collection does not force every document to have the same fields, so two documents in the same collection can have different shapes.
The mental model: a relational database is a set of spreadsheets you cross-reference (the row in orders points at a row in customers by id). MongoDB is a filing cabinet of folders: each folder holds everything about one customer, so the common case, "show me this customer and their orders", is a single read of a single folder. The design rule MongoDB people repeat is "data that is accessed together should be stored together."
It is not a toy key-value store. MongoDB has a rich query language, secondary indexes of many kinds, an aggregation pipeline (its answer to GROUP BY, joins and reporting), multi-document ACID transactions (since 4.0), replication for high availability (replica sets) and sharding for horizontal scaling. The server is written in C++ and stores data with the WiredTiger storage engine.
db.customers.insertOne({
_id: 1,
name: "Rohit",
email: "rohit@example.com",
address: { city: "Toronto", country: "CA" },
orders: [
{ orderId: 1, status: "shipped", items: [
{ product: "ThinkBook 14", qty: 1, price: 899 },
{ product: "USB-C Charger", qty: 2, price: 29 } ] },
{ orderId: 2, status: "paid", items: [
{ product: "SQL Performance Explained", qty: 1, price: 39 },
{ product: "Designing Data-Intensive Applications", qty: 1, price: 49 } ] }
]
})
db.customers.findOne({ "orders.status": "paid" }){ acknowledged: true, insertedId: 1 }
{
_id: 1,
name: 'Rohit',
email: 'rohit@example.com',
address: { city: 'Toronto', country: 'CA' },
orders: [
{
orderId: 1,
status: 'shipped',
items: [
{ product: 'ThinkBook 14', qty: 1, price: 899 },
{ product: 'USB-C Charger', qty: 2, price: 29 }
]
},
{
orderId: 2,
status: 'paid',
items: [
{ product: 'SQL Performance Explained', qty: 1, price: 39 },
{ product: 'Designing Data-Intensive Applications', qty: 1, price: 49 }
]
}
]
}dbis the current database (ch16here) anddb.customersis a collection handle. The collection doesn't exist yet; MongoDB creates it on the first insert, so there was no schema to declare.insertOnesends one document. It has a nested object (address) and an array of nested objects (orders), and each order has its own array (items). This is the "folder" — one customer and everything about them.- The server replies
{ acknowledged: true, insertedId: 1 }: the write was confirmed and the document's primary key is the_id: 1we supplied (if we hadn't, the shell would have generated an ObjectId). findOne({ "orders.status": "paid" })uses dot notation to reach into the array: "any element oforderswhosestatusispaid". No join, no second query.- The whole document comes back in one read, orders and items included. That is the core trade: reads of the "whole thing" are cheap and simple, at the cost of deciding up front how your data is grouped (see Embedding vs referencing).
The same data in the site's MySQL sample schema takes four tables and three joins to reassemble:
SELECT c.name, o.id AS order_id, o.status, p.name AS product, oi.quantity, oi.unit_price
FROM customers c
JOIN orders o ON o.customer_id = c.id
JOIN order_items oi ON oi.order_id = o.id
JOIN products p ON p.id = oi.product_id
WHERE c.id = 1
ORDER BY o.id, p.name;+-------+----------+---------+---------------------------------------+----------+------------+
| name | order_id | status | product | quantity | unit_price |
+-------+----------+---------+---------------------------------------+----------+------------+
| Rohit | 1 | shipped | ThinkBook 14 | 1 | 899.00 |
| Rohit | 1 | shipped | USB-C Charger | 2 | 29.00 |
| Rohit | 2 | paid | Designing Data-Intensive Applications | 1 | 49.00 |
| Rohit | 2 | paid | SQL Performance Explained | 1 | 39.00 |
+-------+----------+---------+---------------------------------------+----------+------------+- In the relational design, a customer is one row in
customers, each order is a row inorders, and each line of an order is a row inorder_items, with the product name in yet another table,products. - To show "Rohit and his orders" the database joins all four, matching foreign keys (
o.customer_id = c.id, and so on). - The result is flat: four rows, Rohit's name repeated on every one and each order's status repeated per item. The application has to fold them back into "one customer, two orders, four items" itself.
- The MongoDB document stored that nesting directly. Neither approach is "better" in general: the relational one avoids duplication and makes ad-hoc questions ("best-selling product across all customers") easy, while the document one makes the common read path one lookup. Chapter 21 is about choosing.
Flexible shape means a second customer can look completely different, and MongoDB accepts it without complaint:
db.customers.insertOne({
_id: 2,
name: "Maya",
phones: ["+91 98200 00000", "+91 22 2000 0000"],
vip: true
})
db.customers.find({}, { name: 1, address: 1, phones: 1, vip: 1 }){ acknowledged: true, insertedId: 2 }
[
{ _id: 1, name: 'Rohit', address: { city: 'Toronto', country: 'CA' } },
{
_id: 2,
name: 'Maya',
phones: [ '+91 98200 00000', '+91 22 2000 0000' ],
vip: true
}
]- Maya has no
email, noaddressand noorders, but she has aphonesarray and avipflag that Rohit doesn't. The insert succeeds anyway: by default a collection does not validate the shape of its documents. find({}, { … })means "every document, but only these fields" (a projection). Each document returns only the fields it actually has;_idcomes back even though we didn't ask for it.- Rohit has no
phones, so it is simply absent from his result, notnull. Missing andnullare different things in MongoDB, and queries treat them slightly differently ($exists and $type). - This flexibility is why MongoDB is popular for evolving products: you can start writing a new field today without a migration. The flip side is in the next box.
Where MongoDB fits well, and where it fits less well:
- Good fits: product catalogues where every category has different attributes, user profiles and settings, content management, event and activity logs, IoT and time-series data (it has dedicated time-series collections), mobile and web back ends whose data is naturally JSON, and anything where the read path is "fetch one aggregate by id".
- Less natural fits: heavily relational data with many many-to-many relationships queried from every direction, ad-hoc reporting across many entities (possible with aggregation, but SQL is more expressive), and workloads where almost every operation must update several entities atomically (transactions work, but if everything needs them, a relational database is the more natural tool).