MongoDB – Installation and mongosh

September 24, 20246 min readUpdated 8/24/2026

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 mongosh

That 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 sets

Nothing 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/db

Two 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 folau

Two 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 shape

use 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 --jsonArray

mongodump 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.
  • --replSet starts 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.
  • mongosh is JavaScript, and writes create databases and collections on demand.