Skip to content

What is MongoDB

MongoDB is a document database. That single phrase carries the whole idea: the thing it stores is a document, a self-contained JSON-like object, and a group of documents is a collection. Where a relational database asks you to spread one logical record across several tables and stitch it back together with joins, MongoDB lets a record be one document that already holds everything that belongs together — nested objects and arrays included.

The practical effect is that the data in the database tends to look like the objects in your program. A member with a name, a fine balance, and a list of borrowed books is one document, not a row in a members table plus rows in a loans table that you have to join every time you want the full picture.

In a relational world a member and their borrowed books live in two tables linked by a key. In MongoDB the same information is one document with an embedded array.

flowchart LR
  subgraph RDBMS["Relational: two tables joined by a key"]
    direction TB
    T1["members table
 id | name | fines"]
    T2["loans table
 id | member_id | title"]
    T1 -->|"member_id = id"| T2
  end
  subgraph MONGO["MongoDB: one document"]
    direction TB
    D["members collection
 one document:
 name, fines, borrowed[ ... ]"]
  end
  RDBMS -.->|"same data, different shape"| MONGO
The same record as joined relational rows versus a single MongoDB document

Here is that document, run for real. This fence inserts the member, reads it back, and asks the server who it is — the output below each fence is what mongosh 2.11.1 printed on this Mac against mongo:8.3.11 (single-node replica set), not hand-written JSON:

mongosh/what-is.js
print(db.version()); // server version
print(db.hello().isWritablePrimary); // this node accepts writes
print(db.hello().maxBsonObjectSize); // the per-document ceiling, in bytes
db.members.insertOne({ name: "Grace", fines: 0, borrowed: ["Compilers", "Algorithms"] });
const grace = db.members.findOne({ name: "Grace" });
console.log(grace);
assert.strictEqual(grace.borrowed.length, 2); // the array lives INSIDE the document: no loans table, no join
assert.strictEqual(db.hello().maxBsonObjectSize, 16777216);
8.3.11
true
16777216
{
_id: ObjectId('6abca2af5967435115bd62b0'),
name: 'Grace',
fines: 0,
borrowed: [ 'Compilers', 'Algorithms' ]
}

Two things to notice. The _id is a real ObjectId('...') (a BSON type, not the string that plain JSON would give you — the next lessons unpack it), and maxBsonObjectSize is 16777216 bytes: 16 MiB is the hard ceiling for one document, which is why “embed everything” has a limit.

A collection does not force every document to have the same fields. One member document can carry an email, another can leave it out, and a third can add a phone that no one else has. MongoDB does not reject the odd one out. This is called a flexible (or dynamic) schema, and it is the feature people notice first.

Flexibility is a convenience, not an excuse for chaos. In practice you keep documents in a collection mostly consistent on purpose, and MongoDB lets you add schema validation rules when you want the database to enforce a shape. The point is that the schema lives in your hands and can evolve field by field, without a migration that rewrites every existing record up front.

mongosh/what-is-flexible.js
db.people.insertOne({ name: "Ada", fines: 0 });
db.people.insertOne({ name: "Alan", fines: 2, email: "[email protected]" });
console.log(db.people.find({}, { _id: 0 }).toArray());
assert.strictEqual(db.people.countDocuments({ email: { $exists: true } }), 1);
[
{ name: 'Ada', fines: 0 },
{ name: 'Alan', fines: 2, email: '[email protected]' }
]

Both of these are perfectly valid members in the same collection, even though only one has an email.

MongoDB tends to be the comfortable choice when:

  • Your data is naturally hierarchical or varied. Documents, profiles, product catalogues, events, and content where records do not all share the same fields map cleanly onto documents.
  • You read and write whole objects together. If your app almost always wants the member and their borrowed books at once, embedding them in one document means one fast read instead of a join.
  • Your schema is still moving. Early products change shape constantly; a flexible schema lets you add a field today without a migration.
  • You need to scale writes horizontally. MongoDB shards across many machines, which the later Operations & Scaling module covers.

When a relational database is the better tool

Section titled “When a relational database is the better tool”

A document database is not always the right answer. Reach for a relational database (PostgreSQL, MySQL, and friends) when:

  • Your data is highly relational and uniform. Lots of many-to-many relationships between equally important entities are exactly what SQL joins were built for.
  • You need multi-row transactions as the everyday case. MongoDB supports multi-document transactions, but a banking-style ledger where almost every operation spans several records often fits a relational model more naturally.
  • You rely on rich ad-hoc SQL and a mature reporting ecosystem. Decades of SQL tooling, BI integrations, and analyst familiarity are a real advantage.
  • Strict, enforced-up-front schemas are a feature you want, not a constraint you are trying to escape.

The honest summary: pick the database whose natural shape matches your data and your access patterns. MongoDB shines when records are self-contained objects you read and write as a unit.

  • “Flexible schema” does not mean “no schema.” It means the schema is your responsibility and can change gradually. Most healthy collections are quite consistent in practice.
  • MongoDB is not a key-value store and not schemaless JSON dumped on disk. It indexes fields, runs rich queries, and validates shapes when you ask it to.
  • The choice is rarely all-or-nothing. Many systems use MongoDB for some workloads and a relational database for others.

An assistant asked for “a members collection in MongoDB” will often reassure you that the database is schemaless, so nothing needs to be declared. The code looks harmless:

🐛 Spot the bug

// AI comment: "MongoDB is schemaless, so we can just insert - no setup needed"
db.members.insertMany([
{ name: "Ada", fines: 0 },
{ name: "Alan", fines: 2 },
]);
db.members.insertOne({ name: "Grace", finess: 9 }); // typo in the field name
// later: who owes money?
db.members.countDocuments({ fines: { $gte: 1 } });
Show the answer

The insert is accepted - MongoDB has no schema to reject it against. Grace owes 9, but her document has the misspelled field finess, so the count is 1 (only Alan) instead of 2, and no error ever fires. A find({ fines: { $exists: false } }) would have exposed her: [ { name: 'Grace', finess: 9 } ]. “Flexible” means the shape is your responsibility. The fix is to make the database enforce the shape with a $jsonSchema validator (proven next; the schema-validation lesson covers it in depth):

mongosh/what-is-validator.js
db.createCollection("checked", {
validator: { $jsonSchema: {
required: ["name", "fines"],
properties: { _id: {}, name: { bsonType: "string" }, fines: { bsonType: "int" } },
additionalProperties: false, // without `_id: {}` above, this would reject EVERY insert
} },
});
let rejected = "";
try { db.checked.insertOne({ name: "Grace", finess: 9 }); } catch (e) { rejected = e.message; print(rejected); }
assert.strictEqual(rejected, "Document failed validation");
assert.strictEqual(db.checked.insertOne({ name: "Grace", fines: 9 }).acknowledged, true);
Document failed validation

The typo is now an error at write time (errInfo.details.schemaRulesNotSatisfied lists additionalProperties and required). Note the _id: {} line: with additionalProperties: false the server treats _id as an extra property too - a real gotcha we hit while writing this lesson.

Think of a collection as a shelf of self-describing folders, not a grid. Each folder (document) carries its own field names, so nothing outside the folder needs to agree with it. That is why adding a field is free - and also why a typo is just another valid field. The database gives you storage and queries by default, and shape enforcement only when you ask for it.

ChoiceBenefitCost
Document database (MongoDB)Flexible data shapes, a natural fit for hierarchical or varied data; reads and writes a whole object without a joinSchema is not automatically enforced (the finess typo above was accepted) - consistency is the team’s job, and ad-hoc joins across collections are more limited than SQL
Relational database (PostgreSQL, MySQL)Strict schema from the start, strong support for complex many-to-many joins, and a mature tooling/reporting ecosystemChanging the schema requires a migration touching every existing row, and related data is spread across many tables that then need joining back together
MongoDB with $jsonSchema validationFlexible where you want it, strict where it mattersYou now maintain the schema and its migrations, much like the relational side
  • “Flexible schema” means “no schema” - false. The schema lives in your code and, optionally, in a validator. The finess example above is the failure mode when nobody owns it.
  • MongoDB is a key-value store or JSON dumped on disk - it indexes fields, runs rich queries, and validates shapes when you ask it to. _id values are real ObjectId (BSON) values, not JSON strings.
  • Choosing MongoDB because of hype - heavy, uniform many-to-many joins usually fit a relational database better; choose by access pattern.
  • The system must pick one or the other - many systems use MongoDB for the flexible parts and a relational database for multi-row transactional parts.
  • “No limits on document size” - maxBsonObjectSize printed 16777216 above: 16 MiB per document.

Real-world example

Forbes - migrated the CMS that serves all content on forbes.com to MongoDB Atlas in 2019; its flexible data structures let the team add new components quickly, and Forbes reports average build times cut from 25 minutes to nine (mongodb.com/customers/forbes, fetched 2026-09-30).

Python/Go/Rust are not used in this lesson; every fence above ran in mongosh.

What does MongoDB store as its basic unit of data?
What does "flexible schema" actually mean in MongoDB?
Which workload is usually a better fit for a relational database than MongoDB?
An AI assistant inserts { name: "Grace", finess: 9 } into a collection with no validator. What happens?
What does db.hello().maxBsonObjectSize return on the server used in this course, and what does it mean?