Your API works, but it forgets everything the moment you restart it. In-memory arrays were training wheels. Real applications need a database that survives crashes, stores gigabytes, and serves thousands of queries per second. For the MERN stack, that database is MongoDB.
MongoDB is a document database. Instead of tables and rows, it stores flexible JSON-like documents inside collections. This lesson covers the mental model, the shell, CRUD, and the GUI tool you will use daily.
SQL vs MongoDB, in one table
The concepts map cleanly:
| SQL | MongoDB | Notes |
|---|---|---|
| Database | Database | Same idea |
| Table | Collection | A group of documents |
| Row | Document | A single record |
| Column | Field | A key in the document |
JOIN | $lookup / references | Done in application or aggregation |
| Foreign key | ObjectId reference | Enforced by your app, not the engine |
| Schema migration | None required | Fields can differ between documents |
"Schemaless" does not mean "no design". It means the database will not reject a document because it has an extra field — so your application must enforce structure. That is exactly what Mongoose does in the next lesson.
Documents, collections, and BSON
A MongoDB document is a set of key-value pairs, close to JSON:
{
_id: ObjectId('665f1d2a9c1b4e0012a3b4c5'),
title: 'Spirited Away',
year: 2001,
genres: ['animation', 'fantasy'],
director: { name: 'Hayao Miyazaki' },
createdAt: ISODate('2026-01-05T10:00:00Z')
}Under the hood it is stored as BSON — binary JSON with types JSON lacks:
| BSON type | Use |
|---|---|
ObjectId | 12-byte unique id, generated by the driver |
Date | Real date/time values, timezone-aware |
Decimal128 | Precise decimals for money |
Int32 / Int64 | Sized integers |
Binary | Files and buffers |
Every document gets an _id if you do not provide one, and it is unique within the collection. This is the field every reference and lookup will use.
Get a database in five minutes
You do not need to install anything locally. The free MongoDB Atlas tier (M0) is enough for every project in this curriculum:
- Create an account at mongodb.com/atlas and create a free cluster (choose the region closest to you).
- Under Database Access, create a user with a password.
- Under Network Access, allow your current IP (or
0.0.0.0/0for a throwaway dev cluster, never for production). - Click Connect → Drivers and copy the connection string:
mongodb+srv://USER:PASSWORD@cluster0.abcde.mongodb.net/library?retryWrites=true&w=majorityThe part after the last / before ? is the database name. Keep this string in .env as MONGODB_URI and never commit it.
The shell: mongosh
mongosh is the interactive shell. Atlas offers a browser version (Cluster → Connect → Shell), or install it locally. It is the fastest way to inspect and edit data:
show dbs // list databases
use library // switch (creates on first write)
db.books.insertMany([
{ title: 'Dune', author: 'Frank Herbert', year: 1965, pages: 412, tags: ['sci-fi'] },
{ title: 'Neuromancer', author: 'William Gibson', year: 1984, pages: 271, tags: ['sci-fi', 'cyberpunk'] },
{ title: 'The Hobbit', author: 'J.R.R. Tolkien', year: 1937, pages: 310, tags: ['fantasy'] },
])You just created the library database and the books collection implicitly. MongoDB creates them on first write — no CREATE TABLE ceremony.
Reading
db.books.find() // everything
db.books.find({}, { title: 1, year: 1 }) // projection: only these fields (+_id)
db.books.findOne({ title: 'Dune' }) // one document
db.books.countDocuments({ tags: 'sci-fi' }) // count matchingQuery operators replace your filter logic:
| Operator | Meaning | Example |
|---|---|---|
$gt / $gte / $lt / $lte | Comparison | { year: { $gte: 1950 } } |
$in / $nin | In a list | { tags: { $in: ['cyberpunk'] } } |
$ne | Not equal | { author: { $ne: 'Unknown' } } |
$exists | Field present | { rating: { $exists: true } } |
$regex | Pattern match | { title: { $regex: /^the /i } } |
$and / $or | Combine conditions | { $or: [{ year: 1965 }, { year: 1984 }] } |
Sorting, pagination, and shaping:
db.books
.find({ year: { $gte: 1950 } })
.sort({ year: -1 }) // -1 descending, 1 ascending
.skip(0)
.limit(10)find takes two objects: the filter and the projection. The projection is how you avoid sending entire documents to a client that only needs three fields.
Updating
db.books.updateOne(
{ title: 'Dune' },
{ $set: { rating: 4.8, updatedAt: new Date() } }
)
db.books.updateMany(
{ tags: 'sci-fi' },
{ $inc: { copiesSold: 1 } } // increments a number
)
db.books.updateOne(
{ title: 'Dune' },
{ $push: { tags: 'classic' } } // appends to an array
)Update operators you will use constantly: $set, $unset, $inc, $push, $pull, $addToSet. Without an operator, an update replaces the whole document — a common and painful mistake.
Deleting
db.books.deleteOne({ title: 'The Hobbit' })
db.books.deleteMany({ rating: { $lt: 2 } })There is no undo. Take a backup or use a soft-delete field (deletedAt) for user-facing data.
Compass: the GUI
MongoDB Compass connects with the same connection string and gives you:
- A list of databases and collections with document counts.
- A visual query builder — filters, projections, sorts without syntax.
- Document editing, schema analysis, and a live index panel.
- An aggregation pipeline builder that shows results after each stage (invaluable for the next lessons).
Use the shell for speed and scripts; use Compass for exploring unfamiliar data and building queries visually.
The Node driver, briefly
Mongoose (next lesson) is the tool you will use in the API, but it is built on the official driver:
import { MongoClient } from 'mongodb'
const client = new MongoClient(process.env.MONGODB_URI)
await client.connect()
const books = client.db('library').collection('books')
const sciFi = await books.find({ tags: 'sci-fi' }).toArray()
console.log(sciFi)
await client.close()This works, but you would end up writing schema validation, casting, and relationship logic by hand. Mongoose exists to do that for you.
Try it
Open mongosh (or the Atlas browser shell) and run these one at a time. Read the output of each command.
use practice
db.movies.insertMany([
{ title: 'Akira', year: 1988, genres: ['animation'] },
{ title: 'Perfect Blue', year: 1997, genres: ['thriller'] },
{ title: 'Your Name', year: 2016, genres: ['romance'] },
])
db.movies.find({ year: { $gt: 1990 } }).sort({ year: 1 })
db.movies.updateOne({ title: 'Akira' }, { $set: { rating: 8.2 } })
db.movies.deleteOne({ title: 'Perfect Blue' })
db.movies.countDocuments()Check your understanding
In MongoDB terminology, what is the equivalent of a SQL table?
Which query returns only books published in 1980 or later?
Set up Atlas and practice CRUD in the shell
- Create a free Atlas cluster. Add a database user and allow your IP.
- Copy the connection string and, if you use the shell locally, connect with
mongosh "YOUR_URI". - In the
librarydatabase, create abookscollection with at least five documents containingtitle,author,year,pages, andtags. - Run and record the output of:
- all books published after 1980, sorted newest first
- books with at least two tags (hint: compare array size or use
$all) - a projection returning only
titleandyear updateOneadding aratingfieldupdateManypushing aclassictag onto books published before 1970deleteOneremoving the lowest-rated book
- Open Compass with the same connection string and find the same data without writing a query.
- Save every command in a
commands.jsfile in your project — it doubles as notes and as documentation.
Summary
- MongoDB stores BSON documents in collections; no migrations, but your app must enforce shape.
- Every document has a unique
_id; references areObjectIdvalues pointing at other documents. - Atlas free tier gives you a cloud database in minutes; keep
MONGODB_URIin.env. mongoshcovers CRUD:insertMany,find,updateOne/updateMany,deleteOne,countDocuments.- Query operators (
$gte,$in,$regex,$or) replace most filter logic. - Updates need operators (
$set,$inc,$push); without one, the document is replaced. - Compass visualizes data, builds queries, and will help you learn aggregation.
Next: Mongoose — schemas, validation, and models that turn these loose documents into a reliable data layer.