The shell and Compass
There are three ways you will talk to MongoDB: the mongosh shell for quick, scriptable commands; MongoDB Compass, the official graphical client, for clicking around and exploring; and a driver in your application’s language for the real work. This lesson covers all three, ending with how to open a connection from each of the five environments this course uses.
Every one of them connects with the same kind of address - a connection string - so once you understand that, switching between tools is trivial. Everything below was run on mongo 8.3.11 with mongosh 2.11.1 and Node driver 7.7.0 on one Mac; the interactive transcript comes from a throwaway mongo:8 container with a library database holding 25 members.
mongosh basics
Section titled “mongosh basics”mongosh is an interactive JavaScript shell. You type a command, it runs against the server, and it prints the result. Here is a real session (the test> / library> prompts are the shell’s own; lines you type follow them):
test> show dbsadmin 100.00 KiBconfig 12.00 KiBlibrary 8.00 KiBlocal 40.00 KiBtest> use libraryswitched to db librarylibrary> db.members.find({}, { _id: 0, name: 1 })[ { name: 'Member 1' }, { name: 'Member 2' }, ... { name: 'Member 19' }, { name: 'Member 20' }]Type "it" for morelibrary> it[ { name: 'Member 21' }, { name: 'Member 22' }, { name: 'Member 23' }, { name: 'Member 24' }, { name: 'Member 25' }]library> itno cursorshow dbslists the databases;use libraryselects one (it need not exist yet - MongoDB creates it at the first write);db.members.find()reads.- The shell prints 20 documents per batch (
config.get("displayBatchSize")returned20), thenType "it" for more.itstands for iterate, and once the cursor is exhausted it answersno cursor.
show, use and it are shell-only commands, not JavaScript: they work at the prompt but not inside a script, an --eval string, or a driver. Every one has a plain-JavaScript equivalent that does work everywhere, and this fence proves it (it seeds its own library members first):
db.members.insertMany(Array.from({ length: 25 }, (_, i) => ({ name: `Member ${i + 1}`, joined: 2020 + (i % 6), fines: i % 4 })));
const lib = db.getSiblingDB("library_demo"); // like `use library_demo`lib.members.insertOne({ name: "Grace" });const names = db.getMongo().getDBNames(); // like `show dbs`console.log(names.includes("library_demo")); // true: created by the first write
const cursor = db.members.find({}, { _id: 0, name: 1 });console.log(cursor.next(), cursor.hasNext()); // like `it`, one document at a timeconsole.log(db.members.find().toArray().length); // or drain the whole cursorconsole.log(config.get("displayBatchSize")); // the interactive page size
assert.strictEqual(db.members.find().toArray().length, 25);assert.strictEqual(config.get("displayBatchSize"), 20);db.getSiblingDB("library_demo").dropDatabase();true{ name: 'Member 1' } true2520Running mongosh without typing: --eval and --file
Section titled “Running mongosh without typing: --eval and --file”For scripts and cron jobs you do not sit at a prompt. --eval runs one expression, --file runs a script, and --quiet suppresses the banner. These ran against the throwaway container (with credentials, see the next section):
mongosh --quiet "mongodb://root:secret@localhost:27017/library?authSource=admin" \ --eval 'db.members.countDocuments({ fines: { $gt: 1 } })'
mongosh --quiet "mongodb://root:secret@localhost:27017" --file report.js121 of 2 members owe fineswhere report.js is an ordinary script - note console.log, not show:
const library = db.getSiblingDB("report_demo");library.members.insertMany([{ name: "Ada", fines: 0 }, { name: "Alan", fines: 3 }]);const total = library.members.countDocuments();const owing = library.members.countDocuments({ fines: { $gt: 0 } });console.log(`${owing} of ${total} members owe fines`);assert.strictEqual(owing, 1);library.dropDatabase();A tour of Compass
Section titled “A tour of Compass”MongoDB Compass is the official desktop GUI, and it is the friendliest way to explore data you do not yet understand. You connect by pasting a connection string into the box on the start screen and clicking Connect. Compass also embeds a MongoDB shell.
Once connected, the sidebar lists your databases and collections. The Compass documentation (mongodb.com/docs/compass, fetched 2026-09-30) names these tabs for a collection:
- Documents - insert, browse and query documents; edit a field in place.
- Aggregations - a visual pipeline builder: add stages one at a time and watch the documents transform (the aggregation module leans on it).
- Schema - samples the collection and shows which fields exist and what types they hold.
- Explain Plan - how MongoDB executed a query, index or full scan (the heart of the indexes module).
- Indexes - list, create and drop indexes.
- Validation - schema validation rules.
Compass is a desktop app, so we could not run it in the same automated way as the rest of this lesson; its layout changes between releases, so trust the docs page over any screenshot. Compass and the shell are complementary: Compass for seeing and exploring, the shell for repeating and scripting. Neither replaces a driver in your application code.
Connecting from each environment
Section titled “Connecting from each environment”When it is time to write an application, you connect with a driver. The connection string is the same; only the API around it differs. Here is how to open a connection in each of the five environments. Python, Go, Rust and Node each printed connected when we ran the snippets below verbatim against a plain mongo:8 container on port 27017.
mongosh "mongodb://localhost:27017"// app/connect.mjs @skip-verify ran verbatim against a throwaway mongo:8 on host port 27017import { MongoClient } from "mongodb";
const client = new MongoClient("mongodb://localhost:27017");await client.connect();await client.db("library").command({ ping: 1 });console.log("connected");from pymongo import MongoClient
client = MongoClient("mongodb://localhost:27017")client.admin.command("ping")print("connected")client, err := mongo.Connect(options.Client().ApplyURI("mongodb://localhost:27017"))if err != nil { return err}if err := client.Ping(ctx, nil); err != nil { return err}fmt.Println("connected")let client = Client::with_uri_str("mongodb://localhost:27017").await?;client .database("library") .run_command(doc! { "ping": 1 }) .await?;println!("connected");(Versions used: PyMongo 4.18.2, Go driver v2.9.1, Rust driver 3.9.1, Node driver 7.7.0. The Python, Go and Rust tabs were run in scratch programs that supply client/ctx around the snippet.)
Connection-string options that bite
Section titled “Connection-string options that bite”The format is mongodb://[user:password@]host[:port][/database][?options]. Three options decide most “it worked yesterday” problems. We ran each against a real server with a 2 second serverSelectionTimeoutMS:
// app/connection-options.mjs @skip-verify needs a second, auth-enabled container on host port 27019import { MongoClient } from "mongodb";
async function tryUri(label, uri) { const client = new MongoClient(uri, { serverSelectionTimeoutMS: 2000 }); try { await client.connect(); await client.db("library").collection("members").findOne(); console.log(label, "-> ok"); } catch (e) { console.log(label, "->", e.name + ":", e.message.split("\n")[0]); } finally { await client.close(); }}await tryUri("no authSource ", "mongodb://root:secret@localhost:27019/library");await tryUri("authSource=admin", "mongodb://root:secret@localhost:27019/library?authSource=admin");await tryUri("replicaSet=rs0 ", "mongodb://localhost:27117/?replicaSet=rs0");await tryUri("replicaSet=nope ", "mongodb://localhost:27117/?replicaSet=nope");await tryUri("directConnection", "mongodb://localhost:27117/?directConnection=true");no authSource -> MongoServerError: Authentication failed.authSource=admin -> okreplicaSet=rs0 -> okreplicaSet=nope -> MongoServerSelectionError: Server selection timed out after 2000 msdirectConnection -> okauthSource- the root user was created in theadmindatabase. When the string names a database (/library) and noauthSource, the driver authenticates against that database, where the user does not exist. Same credentials,Authentication failed.until you add?authSource=admin. With no database in the path the default isadmin.replicaSet- must match the set’s real name; a wrong name never finds a matching member, so the driver times out with a server-selection error rather than “wrong name”.directConnection=true- skip discovery and talk only to the address you gave (useful when the set advertises a hostname your machine cannot reach, see the Docker lesson).
Reuse one client
Section titled “Reuse one client”Once a client is connected, you reuse it for the whole life of your program. A client owns a connection pool (plus monitoring connections to the server) and is meant to be shared. We measured the difference with 50 sequential “requests” on a local server:
50 requests, new client each, never closed: 203 ms, connections +10050 requests, one shared client: 33 ms, connections +2(Second run: 162 ms vs 36 ms; +100 vs +2 both times.) Two server connections per client leaked, and about six times the latency, on a local socket. Across a network with TLS the gap only widens.
Choosing a tool
Section titled “Choosing a tool”flowchart TD Q["What do you want to do"] --> S["Quick one-off command or a script"] Q --> C["Explore and understand unfamiliar data"] Q --> D["Build a feature in your app"] S --> SH["mongosh shell"] C --> CO["Compass GUI"] D --> DR["A driver in your language"]
Tips and gotchas
Section titled “Tips and gotchas”- The connection string format is
mongodb://[user:password@]host:port[/database][?options]. The host is the only required part. - Use the shell vs Compass vs a driver by intent: the shell for fast and repeatable commands, Compass for visual exploration and schema discovery, and a driver for anything your application does in production.
- Authentication changes the string, not the workflow. If you set a root user in the Docker lesson, include
root:secret@before the host in every tool, andauthSource=adminwhen the path names another database. show,use,itare not JavaScript. Inside a script usedb.getSiblingDB(...),db.getMongo().getDBNames()and cursor methods.
Reading AI-generated code
Section titled “Reading AI-generated code”An assistant writing an Express endpoint on top of MongoDB will often “keep the handler self-contained”:
🐛 Spot the bug
// AI comment: "self-contained handler - connects, queries, done"app.get("/members/:name", async (req, res) => {const client = new MongoClient(process.env.MONGO_URL);const member = await client.db("library").collection("members").findOne({ name: req.params.name });res.json(member);});Show the answer
Every request builds a new client, connects it, and never closes it. We ran exactly that loop 50 times against a local server: it took 203 ms (162 ms on a second run) against 33 ms for one shared client, and the server’s connection count went up by 100, two per abandoned client (a connection for the pool and one for monitoring). Under real traffic the connection count climbs until the server’s or OS’s limit is hit and requests start failing. The fix is a single client created once at startup and reused (and closed on shutdown):
// One client for the whole process: this is what the prelude's `client` is.const members = client.db("library").collection("members");await members.insertOne({ name: "Grace" });const member = await members.findOne({ name: "Grace" }); // called per request, same poolif (member?.name !== "Grace") throw new Error("expected Grace");console.log(member.name);GraceMental Model
Section titled “Mental Model”A connection string is an address plus instructions: where to knock (host:port), who you are (user:password), where your account lives (authSource), and what topology to expect (replicaSet, directConnection). The shell, Compass and drivers are three front ends to the same door. The only stateful object is the client - a pool of long-lived connections - and that is why it is created once.
Trade-offs
Section titled “Trade-offs”| Choice | Benefit | Cost |
|---|---|---|
| mongosh | Fast to script and repeat, runs over SSH on a server with no GUI, --eval / --file fit cron and CI | You need to remember command syntax and field names yourself; show/use/it do not work outside the prompt |
| Compass (GUI) | Instantly see the schema and real documents, build an aggregation pipeline stage by stage, understand new data faster | Needs a screen - cannot be used over SSH or automated - and is heavier than one terminal command |
| One shared driver client | 33 ms vs 203 ms for 50 requests, +2 connections vs +100 (measured above) | You must manage its lifecycle (create at startup, close on shutdown) |
Common Misconceptions
Section titled “Common Misconceptions”- “Compass is a good tool to automate routine jobs.” - It is built for click-to-explore. Work that runs every day should be a mongosh
--filescript or driver code. - “A new client per request is fine, the driver pools anyway.” - The pool lives inside the client; a new client is a new pool. Measured:
connections +100for 50 requests. - “mongosh and a driver share identical syntax.” -
use,show dbs,itexist only at the shell prompt. The drivers use their own APIs. - “Same credentials, so any connection string works.” - without
authSource=adminthe string above failed withAuthentication failed. - “A wrong
replicaSetname gives a clear error.” - it gives a 2 second server-selection timeout.
Real-world example
The driver itself encodes this rule: a
MongoClientcreated with no options reportsmaxPoolSize100 andminPoolSize0 (Node driver 7.7.0,client.options), a pool sized per process - not per request.