Home / Databases / MongoDB

DatabasesHow-toBeginner

MongoDB basics: connect with mongosh, read and write documents, add your first index

A first hands-on MongoDB session - connect with mongosh, understand databases, collections and documents, insert, query, update and delete safely, add an index, read explain output and fix common connection errors.

Deepak Bhardwaj5 min read

Applies toMongoDB 6.0 and later, mongosh

The goal

You've been asked to look after a MongoDB database, and you've only worked with SQL so far. This post gets you connected and doing useful work, and covers the mistakes that matter most.

SQL term MongoDB term
Database Database
Table Collection
Row Document (JSON-like)
Column Field
Primary key _id
JOIN Embedded documents or $lookup

Step 1: get a test server

Use a throwaway server, not production, to practice on:

docker run -d --name mongo-dev -p 27017:27017 mongo:7
docker exec -it mongo-dev mongosh

If you already have a server, connect like this:

mongosh "mongodb://appuser@db01.contoso.com:27017/shop?authSource=admin"

mongosh prompts for the password, so it won't end up in your shell history. For a replica set, list the hosts, or use the mongodb+srv:// address that Atlas gives you:

mongosh "mongodb://db01:27017,db02:27017,db03:27017/shop?replicaSet=rs0&authSource=admin"

Step 2: look around

show dbs
use shop
show collections
db.getName()
db.stats()
db.version()

use shop doesn't create anything by itself. A database appears when you first write to it.

Step 3: insert documents

db.orders.insertOne({
  orderId: 1001,
  customer: { name: "Asha", city: "Pune" },
  items: [ { sku: "A1", qty: 2 }, { sku: "B7", qty: 1 } ],
  total: 450.50,
  status: "new",
  createdAt: new Date()
})

db.orders.insertMany([
  { orderId: 1002, customer: { name: "Ravi", city: "Delhi" }, total: 120, status: "paid", createdAt: new Date() },
  { orderId: 1003, customer: { name: "Meera", city: "Pune" }, total: 980, status: "new", createdAt: new Date() }
])

There's no table definition to create. Documents in a collection can have different fields. That's flexible, and it also means typos can create new field names without any error.

Step 4: query

// One document
db.orders.findOne({ orderId: 1001 })

// Filter, and return selected fields
db.orders.find(
  { status: "new", total: { $gt: 100 } },
  { orderId: 1, total: 1, _id: 0 }
)

// Search inside a nested field; note the quotes
db.orders.find({ "customer.city": "Pune" })

// Sort and limit
db.orders.find().sort({ createdAt: -1 }).limit(5)

// Count
db.orders.countDocuments({ status: "new" })

Common operators:

Operator Meaning
$gt, $gte, $lt, $lte Greater or less than
$ne Not equal
$in Matches any in a list
$exists The field is present
$or Either condition

A simple group-by, with an aggregation:

db.orders.aggregate([
  { $match: { status: "new" } },
  { $group: { _id: "$customer.city", orders: { $sum: 1 }, total: { $sum: "$total" } } },
  { $sort: { total: -1 } }
])

Step 5: update and delete safely

db.orders.updateOne(
  { orderId: 1001 },
  { $set: { status: "paid" }, $currentDate: { updatedAt: true } }
)

Always use an operator such as $set. replaceOne replaces the entire document, and you lose every field you didn't include.

Before you change anything, run the same filter with find and check what it matches:

db.orders.countDocuments({ status: "cancelled" })
db.orders.deleteMany({ status: "cancelled" })

Caution

db.orders.deleteMany({}) and db.orders.updateMany({}, ...) affect every document, because an empty filter matches everything. db.orders.drop() removes the whole collection and its indexes. There's no undo, so check that you're in the right database (db.getName()) first, and have a backup.

Step 6: add your first index

Without an index, MongoDB reads every document to answer a query. See this with explain:

db.orders.find({ orderId: 1001 }).explain("executionStats")

Check these fields:

Field What to look for
winningPlan.stage COLLSCAN reads everything; IXSCAN uses an index
totalDocsExamined Should be close to nReturned
executionTimeMillis How long it took

Create an index and try again:

db.orders.createIndex({ orderId: 1 }, { unique: true })
db.orders.createIndex({ status: 1, createdAt: -1 })
db.orders.getIndexes()

For a compound index, put fields you test for equality first, followed by your sort field. Indexes speed up reads, but they use memory and disk and slow down writes, so add only those you need. See slow queries and indexes.

Warning

Index builds on a large production collection use resources and take time. Check the impact first, and run them outside busy periods.

Step 7: create a user

use admin
db.createUser({
  user: "shop_app",
  pwd: passwordPrompt(),
  roles: [ { role: "readWrite", db: "shop" } ]
})

Give each application its own user, with access to only the databases it needs. Turn on authentication on any real server, and don't expose port 27017 to the internet.

Common errors

Error Likely cause Fix
Authentication failed Wrong password, or wrong authSource The user usually lives in admin; add ?authSource=admin
ECONNREFUSED Service down, or bindIp limits it to localhost Check systemctl status mongod and net.bindIp
Server selection timeout Network, firewall or wrong replica set name Test the port, and check rs.status()
not primary You're writing to a secondary Connect using the replica set string
E11000 duplicate key A value breaks a unique index Find the existing document
not authorized on shop Role is missing Grant the role for that database

If you're on a replica set, check its health:

rs.status().members.map(m => ({ name: m.name, state: m.stateStr, health: m.health }))
db.hello().isWritablePrimary

Slow operations running now:

db.currentOp({ active: true, secs_running: { $gt: 5 } })

Prevent it next time

  • Test filters with find or countDocuments before running an update or delete.
  • Use a replica set for anything that matters, even if it's small.
  • Keep the DBA cheat sheet nearby.
  • When you're designing collections, read embed vs reference.