You need three things to follow the rest of this track: a MongoDB server, a shell to talk to it, and an understanding of one setup choice that trips people up later. That choice is running a replica set from the very first command, even on your laptop, even with one node. This lesson explains why.
Getting a server
There are three reasonable ways to get MongoDB, and they suit different situations.
Docker is the right default for development. Nothing is installed on your machine, the version is pinned in a file your colleagues share, and deleting it is one command. Atlas is MongoDB’s hosted service and has a free tier; it is the fastest way to get a real cluster and the right answer for production unless you have a reason to run your own. A native install via Homebrew or apt is fine, but it puts a background service on your machine that you will forget about.
The shortest possible Docker run looks like this:
docker run -d --name mongo -p 27017:27017 mongo:8.0
docker exec -it mongo mongoshThat works, and for the next four lessons it is enough. It is also the setup that will fail confusingly in lesson 12, so it is worth spending a minute now on the alternative.
Why a replica set, even with one node
A replica set is a group of servers holding the same data. You would expect to need one only for redundancy, in production. In fact three features you will want on your laptop are all built on the same mechanism — the oplog, the ordered log of every write that a replica set keeps so its members can catch up:
- Transactions across more than one document.
- Change streams, which let your application subscribe to writes as they happen.
- Resumable reads after a failover.
A standalone mongod has no oplog at all, so none of them work. The error you get is
accurate but does not point at your setup:
MongoServerError: The $changeStream stage is only supported on replica setsNothing in that message says “your docker run was missing a flag”.
Starting as a single-node replica set from day one avoids the whole detour, and costs nothing.
A compose file that does it properly
This is the service definition from ReelCMS, the app this track quotes throughout:
services:
mongo:
image: mongo:8.0
container_name: reelcms-mongo
restart: unless-stopped
command: ["--replSet", "rs0", "--bind_ip_all", "--port", "27018"]
ports:
- "27018:27018"
volumes:
- reelcms-mongo-data:/data/dbTwo details there are not arbitrary.
The port is the same number on both sides. That looks redundant until you know
what a replica set does to a client. A replica set does not just serve the connection you opened
— it hands back its member list, and the driver then reconnects to whatever address
that list advertises, discarding the one you dialled. So the advertised host:port has
to be reachable under that exact spelling from wherever your application runs. Publish
27018:27017 while the set advertises localhost:27017, and the driver
dials 27018, is told the primary is at 27017, and hangs there until it times out with a server
selection error that never mentions ports.
--replSet alone is not enough. The set has to be initiated once,
which is a command you run against the server after it starts:
rs.initiate({
_id: "rs0",
members: [{ _id: 0, host: "localhost:27018" }]
})Until you do, mongod accepts connections happily and then fails every write with
NotWritablePrimary. “The port is open” is not the same as “the
database is ready”, which is worth remembering when you write a health check.
Compose has no declarative way to say “run this once after the server starts”, so the usual pattern is a throwaway second container that initiates the set and exits. Making it tolerate being run twice matters — you will restart this stack often:
try {
rs.initiate({ _id: "rs0", members: [{ _id: 0, host: "localhost:27018" }] });
print("replica set initiated");
} catch (e) {
if (e.codeName === "AlreadyInitialized") { print("already initiated - ok"); }
else { throw e; }
}Connecting
mongosh is the shell. It is a full JavaScript environment, which is why every
example in this track that is not Java looks like JavaScript — because it is.
mongosh "mongodb://localhost:27018/reelcms?replicaSet=rs0"The replicaSet=rs0 parameter matters as much as the host. Without it the driver
connects in single-server mode, which works perfectly for ordinary reads and writes and then fails
only when something needs the oplog. The application looks healthy and its live features are
silently dead — the worst kind of misconfiguration, because nothing complains.
Connecting to Atlas instead
A hosted cluster is already a replica set, so none of the above applies. The connection string
uses the mongodb+srv:// scheme, which asks DNS for the member list rather than listing
hosts:
mongosh "mongodb+srv://cluster0.abc123.mongodb.net/reelcms" --username folauTwo things catch people out. Atlas blocks every IP by default, so a connection that hangs is
usually the access list rather than your credentials. And +srv needs working DNS SRV
lookups, which some corporate networks and VPNs quietly block — if it fails there but works
on your phone’s hotspot, that is what happened.
Finding your way around
Five commands cover almost all navigation:
show dbs // every database on the server
use reelcms // switch to one (it need not exist yet)
show collections // what is in it
db.reels.countDocuments({}) // 16
db.reels.findOne() // one document, to see the shapeuse does not create anything. A database and a collection both spring into
existence on the first write, which is convenient and also means a typo in a collection name gives
you a brand-new empty collection rather than an error. If a query returns nothing, checking
show collections for a near-miss spelling is the cheapest first move.
Because the shell is JavaScript, anything you know about JavaScript works — variables,
loops, functions, and const to hold a result you want to poke at:
const reel = db.reels.findOne({ status: "PUBLISHED" })
reel.stats.views // e.g. 4962 — a live counter, it moves
Object.keys(reel) // the field names on this document
db.reels.find().limit(3).toArray() // an array you can map over
db.reels.distinct("status") // [ 'ARCHIVED', 'DRAFT', 'PUBLISHED', 'SCHEDULED' ]Two ergonomics worth knowing now. .pretty() is no longer needed — modern
mongosh formats output by default. And for anything scripted, --eval
runs a snippet without opening an interactive session, which is how the health check above and
every setup script in this track work:
mongosh --port 27018 --quiet --eval 'db.getSiblingDB("reelcms").reels.countDocuments({})'getSiblingDB is the scripted equivalent of use: use is a
shell convenience that does not exist inside a script.
Getting data in and out
Two command-line tools ship alongside the shell and are worth knowing before you need them.
mongoimport loads JSON or CSV into a collection, which is how most sample datasets
arrive:
mongoimport --uri "mongodb://localhost:27018/reelcms" \
--collection reels --file reels.json --jsonArraymongodump and mongorestore are the pair you want for a real backup:
they write BSON rather than JSON, so types survive the round trip. mongoexport writes
JSON and therefore loses them — an ObjectId comes back as a string, a
Date as text. Use export for feeding another tool, never for a backup you intend to
restore.
The short version
- Docker for development, Atlas for production, native install if you must.
- Run a single-node replica set from the start — transactions, change streams and resumable reads all need the oplog, and a standalone has none.
--replSetstarts it;rs.initiate()makes it usable.- Publish the same port number on both sides, because the set advertises an address the driver will reconnect to.
- Put
replicaSet=in the connection string, or the oplog features fail silently. mongoshis JavaScript, and writes create databases and collections on demand.