🍃

MongoDB

MongoDB shell and driver commands — CRUD, aggregation pipeline, indexes, transactions and Atlas Search

Shell & Database Setup

Connect, switch databases, inspect collections and stats

javascript·Connect and navigate
// Connect via mongosh
mongosh "mongodb+srv://user:pass@cluster.mongodb.net/mydb"

// Switch / create database
use mydb

// List databases
show dbs

// List collections
show collections

// Current database
db.getName()

// Drop database
db.dropDatabase()

// Collection stats
db.users.stats()
db.users.countDocuments()
db.users.estimatedDocumentCount()  // fast, uses metadata

// Server status
db.serverStatus()
db.runCommand({ connectionStatus: 1 })
javascript·Collection management
// Create collection explicitly (optional — auto-created on insert)
db.createCollection("users")

// Create capped collection (fixed size, overwrites oldest docs)
db.createCollection("logs", {
  capped: true,
  size: 10485760,   // 10 MB max
  max: 5000,        // max 5000 documents
})

// Rename collection
db.users.renameCollection("customers")

// Drop collection
db.users.drop()

// Validate collection integrity
db.users.validate({ full: true })

CRUD — Create

Insert single and multiple documents

javascript·insertOne and insertMany
// insertOne — returns { acknowledged, insertedId }
db.users.insertOne({
  name:      "Alice",
  email:     "alice@example.com",
  role:      "admin",
  age:       30,
  tags:      ["dev", "oss"],
  address: {
    city:    "Berlin",
    country: "DE",
  },
  createdAt: new Date(),
})

// insertMany — returns { acknowledged, insertedIds }
db.products.insertMany([
  { name: "Widget",  price: 9.99,  stock: 100, category: "tools" },
  { name: "Gadget",  price: 24.99, stock: 50,  category: "tools" },
  { name: "Doohickey", price: 4.99, stock: 200, category: "misc" },
], { ordered: false })
// ordered: false — continue inserting even if one fails

CRUD — Read

find, projection, sort, limit and query operators

javascript·find and findOne
// Find all documents
db.users.find()
db.users.find().pretty()

// Find with filter
db.users.find({ role: "admin" })
db.users.findOne({ email: "alice@example.com" })

// Projection — include (1) or exclude (0) fields
db.users.find(
  { role: "admin" },
  { name: 1, email: 1, _id: 0 }   // include name+email, exclude _id
)

// Sort, limit, skip
db.products.find()
  .sort({ price: -1 })             // -1 = descending, 1 = ascending
  .limit(10)
  .skip(20)

// Count matching documents
db.orders.countDocuments({ status: "pending" })

// Check existence
db.users.findOne({ email: "alice@example.com" }) !== null
javascript·Query operators
// Comparison
db.products.find({ price: { $gt: 10 } })          // >
db.products.find({ price: { $gte: 10 } })         // >=
db.products.find({ price: { $lt: 50 } })          // <
db.products.find({ price: { $lte: 50 } })         // <=
db.products.find({ price: { $ne: 9.99 } })        // !=
db.products.find({ price: { $in: [9.99, 24.99] } })
db.products.find({ price: { $nin: [9.99, 24.99] } })
db.products.find({ price: { $between: [10, 50] } }) // not real — use $gt + $lt

// Logical
db.products.find({ $and: [{ price: { $gt: 10 } }, { stock: { $gt: 0 } }] })
db.products.find({ $or:  [{ category: "tools" }, { category: "misc" }] })
db.products.find({ $nor: [{ price: { $lt: 5 } }, { stock: 0 }] })
db.products.find({ price: { $not: { $gt: 50 } } })

// Element
db.users.find({ phone: { $exists: true } })        // field exists
db.users.find({ phone: { $exists: false } })
db.users.find({ age:   { $type: "int" } })         // BSON type check
db.users.find({ tags:  { $type: "array" } })

// Evaluation
db.users.find({ name: { $regex: /^ali/i } })       // regex
db.users.find({ $expr: { $gt: ["$revenue", "$cost"] } }) // compare fields
db.users.find({ $where: "this.age > 18" })          // JS (slow, avoid)
javascript·Array and embedded document queries
// Array — exact match (order sensitive)
db.users.find({ tags: ["dev", "oss"] })

// Array contains element
db.users.find({ tags: "dev" })

// Array contains all elements (any order)
db.users.find({ tags: { $all: ["dev", "oss"] } })

// Array element matches condition
db.orders.find({ amounts: { $elemMatch: { $gt: 100, $lt: 500 } } })

// Array size
db.users.find({ tags: { $size: 3 } })

// Query by array index
db.users.find({ "tags.0": "dev" })                 // first element

// Embedded document — dot notation
db.users.find({ "address.city": "Berlin" })
db.users.find({ "address.country": "DE", "address.city": "Berlin" })

// $elemMatch on array of objects
db.orders.find({
  items: {
    $elemMatch: {
      product: "Widget",
      quantity: { $gte: 2 },
    }
  }
})

CRUD — Update

updateOne, updateMany, findOneAndUpdate and update operators

javascript·Update operators
// $set — set field values
db.users.updateOne(
  { email: "alice@example.com" },
  { $set: { name: "Alice Smith", updatedAt: new Date() } }
)

// $unset — remove fields
db.users.updateOne({ _id: id }, { $unset: { legacyField: "" } })

// $inc — increment / decrement
db.products.updateOne({ _id: id }, { $inc: { stock: -1, views: 1 } })

// $mul — multiply
db.products.updateMany({}, { $mul: { price: 1.1 } })  // 10% price increase

// $min / $max — only update if new value is smaller/larger
db.stats.updateOne({ _id: id }, { $min: { lowestPrice: 9.99 } })
db.stats.updateOne({ _id: id }, { $max: { highScore: 9999 } })

// $rename — rename a field
db.users.updateMany({}, { $rename: { "fname": "firstName" } })

// $currentDate — set to current date
db.users.updateOne({ _id: id }, { $currentDate: { updatedAt: true } })

// upsert — insert if no match
db.settings.updateOne(
  { userId: "u123" },
  { $set: { theme: "dark" }, $setOnInsert: { createdAt: new Date() } },
  { upsert: true }
)
javascript·Array update operators
// $push — append to array
db.users.updateOne({ _id: id }, { $push: { tags: "typescript" } })

// $push with $each — append multiple
db.users.updateOne(
  { _id: id },
  { $push: { tags: { $each: ["go", "rust"], $sort: 1, $slice: 10 } } }
)

// $addToSet — add only if not already present (set semantics)
db.users.updateOne({ _id: id }, { $addToSet: { tags: "dev" } })
db.users.updateOne({ _id: id }, { $addToSet: { tags: { $each: ["a", "b"] } } })

// $pull — remove elements matching condition
db.users.updateOne({ _id: id }, { $pull: { tags: "deprecated" } })
db.orders.updateOne({ _id: id }, { $pull: { items: { qty: { $lt: 1 } } } })

// $pop — remove first (-1) or last (1) element
db.users.updateOne({ _id: id }, { $pop: { tags: 1 } })   // remove last
db.users.updateOne({ _id: id }, { $pop: { tags: -1 } })  // remove first

// $ positional — update first matching array element
db.orders.updateOne(
  { _id: id, "items.product": "Widget" },
  { $set: { "items.$.qty": 5 } }
)

// $[] — update all array elements
db.orders.updateMany({}, { $inc: { "items.$[].qty": 0 } })

// $[identifier] — filtered positional
db.orders.updateMany(
  { _id: id },
  { $set: { "items.$[item].discounted": true } },
  { arrayFilters: [{ "item.price": { $gt: 20 } }] }
)
javascript·findOneAndUpdate, replaceOne
// findOneAndUpdate — atomically update and return document
const updated = db.users.findOneAndUpdate(
  { email: "alice@example.com" },
  { $set: { role: "moderator" }, $currentDate: { updatedAt: true } },
  {
    returnDocument: "after",   // "before" returns old doc
    upsert: false,
    projection: { name: 1, role: 1 },
  }
)

// findOneAndReplace — replace entire document
db.users.findOneAndReplace(
  { _id: id },
  { name: "Alice", email: "alice@example.com", role: "admin" },
  { returnDocument: "after" }
)

// findOneAndDelete
const deleted = db.users.findOneAndDelete({ email: "alice@example.com" })

// replaceOne — replace entire document (keeps _id)
db.users.replaceOne(
  { _id: id },
  { name: "Alice Smith", email: "alice@example.com" }
)

// updateMany
db.orders.updateMany(
  { status: "processing", updatedAt: { $lt: new Date("2025-01-01") } },
  { $set: { status: "stalled" } }
)

CRUD — Delete

deleteOne, deleteMany and bulk operations

javascript·Delete and bulk write
// deleteOne — delete first matching document
db.users.deleteOne({ email: "alice@example.com" })

// deleteMany
db.sessions.deleteMany({ expiresAt: { $lt: new Date() } })
db.logs.deleteMany({})   // delete all documents (keeps collection)

// bulkWrite — batch multiple operations efficiently
db.products.bulkWrite([
  { insertOne: { document: { name: "New Widget", price: 5.99 } } },
  {
    updateOne: {
      filter: { name: "Widget" },
      update: { $inc: { stock: -1 } },
    }
  },
  {
    updateMany: {
      filter: { stock: { $lt: 5 } },
      update: { $set: { lowStock: true } },
    }
  },
  { deleteOne: { filter: { discontinued: true } } },
  {
    replaceOne: {
      filter: { _id: id },
      replacement: { name: "Updated", price: 12.99 },
      upsert: true,
    }
  },
], { ordered: false })

Aggregation Pipeline

$match, $group, $lookup, $project, $unwind and more

javascript·Core pipeline stages
db.orders.aggregate([
  // $match — filter (put early to reduce documents)
  { $match: { status: "completed", createdAt: { $gte: new Date("2025-01-01") } } },

  // $group — aggregate by key
  {
    $group: {
      _id:          "$userId",
      totalSpent:   { $sum: "$total" },
      orderCount:   { $sum: 1 },
      avgOrder:     { $avg: "$total" },
      firstOrder:   { $min: "$createdAt" },
      lastOrder:    { $max: "$createdAt" },
      statuses:     { $addToSet: "$status" },
    }
  },

  // $project — shape output
  {
    $project: {
      userId:     "$_id",
      _id:        0,
      totalSpent: { $round: ["$totalSpent", 2] },
      orderCount: 1,
      avgOrder:   { $round: ["$avgOrder", 2] },
    }
  },

  // $sort — sort results
  { $sort: { totalSpent: -1 } },

  // $limit / $skip — pagination
  { $skip: 0 },
  { $limit: 20 },
])
javascript·$lookup, $unwind and $addFields
// $lookup — left outer join to another collection
db.orders.aggregate([
  { $match: { status: "completed" } },

  // Simple lookup
  {
    $lookup: {
      from:         "users",
      localField:   "userId",
      foreignField: "_id",
      as:           "user",
    }
  },

  // $unwind — flatten array (lookup returns array)
  { $unwind: { path: "$user", preserveNullAndEmpty: true } },

  // Pipeline lookup (with conditions / sub-pipeline)
  {
    $lookup: {
      from: "products",
      let:  { itemIds: "$items.productId" },
      pipeline: [
        { $match: { $expr: { $in: ["$_id", "$$itemIds"] } } },
        { $project: { name: 1, price: 1 } },
      ],
      as: "productDetails",
    }
  },

  // $addFields — add or overwrite fields
  {
    $addFields: {
      fullName:    { $concat: ["$user.firstName", " ", "$user.lastName"] },
      itemCount:   { $size: "$items" },
      isLargeOrder: { $gt: ["$total", 500] },
    }
  },

  { $project: { "user.password": 0 } },  // exclude sensitive field
])
javascript·$facet, $bucket and $sortByCount
// $facet — multiple aggregations in one pass
db.products.aggregate([
  { $match: { inStock: true } },
  {
    $facet: {
      // Category breakdown
      byCategory: [
        { $group: { _id: "$category", count: { $sum: 1 } } },
        { $sort: { count: -1 } },
      ],

      // Price distribution buckets
      priceRanges: [
        {
          $bucket: {
            groupBy:    "$price",
            boundaries: [0, 10, 25, 50, 100, 500],
            default:    "500+",
            output: {
              count:    { $sum: 1 },
              avgPrice: { $avg: "$price" },
            },
          }
        }
      ],

      // Total and summary
      summary: [
        {
          $group: {
            _id:      null,
            total:    { $sum: 1 },
            avgPrice: { $avg: "$price" },
            minPrice: { $min: "$price" },
            maxPrice: { $max: "$price" },
          }
        }
      ],
    }
  },
])

// $sortByCount — shorthand for $group + $sort by count
db.orders.aggregate([
  { $unwind: "$tags" },
  { $sortByCount: "$tags" },  // most common tags first
])
javascript·Aggregation expressions and operators
db.orders.aggregate([
  {
    $project: {
      // Arithmetic
      tax:          { $multiply: ["$subtotal", 0.2] },
      withTax:      { $add: ["$subtotal", { $multiply: ["$subtotal", 0.2] }] },
      discounted:   { $subtract: ["$total", "$discount"] },
      pricePerItem: { $divide: ["$total", "$itemCount"] },

      // String
      upperName:    { $toUpper: "$name" },
      slug:         { $toLower: { $replaceAll: { input: "$name", find: " ", replacement: "-" } } },
      initials:     { $substr: ["$name", 0, 1] },
      fullLabel:    { $concat: ["$code", " - ", "$name"] },

      // Date
      year:         { $year: "$createdAt" },
      month:        { $month: "$createdAt" },
      dayOfWeek:    { $dayOfWeek: "$createdAt" },
      formatted:    { $dateToString: { format: "%Y-%m-%d", date: "$createdAt" } },

      // Conditional
      tier: {
        $switch: {
          branches: [
            { case: { $gte: ["$total", 1000] }, then: "platinum" },
            { case: { $gte: ["$total", 500] },  then: "gold" },
            { case: { $gte: ["$total", 100] },  then: "silver" },
          ],
          default: "bronze",
        }
      },

      // Type conversion
      totalNum: { $toDouble: "$totalString" },
      idStr:    { $toString: "$_id" },
    }
  },
])

Indexes

Create, inspect and manage indexes for performance

javascript·Creating indexes
// List existing indexes
db.users.getIndexes()

// Single field
db.users.createIndex({ email: 1 })              // ascending
db.users.createIndex({ createdAt: -1 })         // descending

// Unique index
db.users.createIndex({ email: 1 }, { unique: true })

// Compound index — field order matters
db.orders.createIndex({ userId: 1, status: 1, createdAt: -1 })

// Sparse — only index documents where field exists
db.users.createIndex({ phone: 1 }, { sparse: true })

// Partial — only index documents matching condition
db.orders.createIndex(
  { userId: 1, total: -1 },
  { partialFilterExpression: { status: "completed" } }
)

// TTL — auto-delete documents after N seconds
db.sessions.createIndex({ expiresAt: 1 }, { expireAfterSeconds: 0 })
db.logs.createIndex({ createdAt: 1 }, { expireAfterSeconds: 2592000 }) // 30 days

// Text index — full-text search
db.posts.createIndex({ title: "text", body: "text" })
db.posts.createIndex({ title: "text", body: "text" }, { weights: { title: 10, body: 1 } })

// Wildcard index — all fields or a subtree
db.products.createIndex({ "metadata.$**": 1 })

// 2dsphere — geospatial
db.places.createIndex({ location: "2dsphere" })

// Named index
db.users.createIndex({ role: 1 }, { name: "idx_role" })

// Drop index
db.users.dropIndex("idx_role")
db.users.dropIndex({ email: 1 })
javascript·explain() and index hints
// Analyse query execution plan
db.orders.find({ userId: "u123", status: "pending" }).explain("executionStats")

// Key fields in executionStats:
// executionStats.nReturned        — docs returned
// executionStats.totalDocsExamined — docs scanned (want close to nReturned)
// executionStats.totalKeysExamined — index keys examined
// executionStats.executionTimeMillis
// winningPlan.stage:
//   COLLSCAN  — full collection scan (no index)
//   IXSCAN    — index scan
//   FETCH     — fetch docs from heap after index lookup
//   PROJECTION — projection applied
//   SORT      — in-memory sort (can be slow for large sets)

// Force a specific index
db.orders.find({ userId: "u123" }).hint({ userId: 1, status: 1 })
db.orders.find({ userId: "u123" }).hint("userId_1_status_1")  // by name

// Force collection scan (bypass all indexes)
db.orders.find({ userId: "u123" }).hint({ $natural: 1 })

Transactions

Multi-document ACID transactions across collections

javascript·Multi-document transactions
// Transactions require a replica set or sharded cluster
const session = db.getMongo().startSession()

session.startTransaction({
  readConcern:  { level: "snapshot" },
  writeConcern: { w: "majority" },
})

try {
  const accounts = session.getDatabase("bank").accounts

  // Debit sender
  accounts.updateOne(
    { _id: "acc_sender", balance: { $gte: 500 } },
    { $inc: { balance: -500 } },
    { session }
  )

  // Credit receiver
  accounts.updateOne(
    { _id: "acc_receiver" },
    { $inc: { balance: 500 } },
    { session }
  )

  session.commitTransaction()
  console.log("Transfer committed")
} catch (err) {
  session.abortTransaction()
  console.error("Transaction aborted:", err)
} finally {
  session.endSession()
}
javascript·Transactions in Node.js driver
import { MongoClient } from "mongodb"

const client = new MongoClient(process.env.MONGODB_URI)

async function transferFunds(fromId, toId, amount) {
  const session = client.startSession()

  try {
    await session.withTransaction(async () => {
      const accounts = client.db("bank").collection("accounts")

      const sender = await accounts.findOne(
        { _id: fromId },
        { session }
      )
      if (!sender || sender.balance < amount) {
        throw new Error("Insufficient funds")
      }

      await accounts.updateOne(
        { _id: fromId },
        { $inc: { balance: -amount } },
        { session }
      )

      await accounts.updateOne(
        { _id: toId },
        { $inc: { balance: amount } },
        { session }
      )

      // Audit log (same transaction)
      await client.db("bank").collection("transfers").insertOne({
        from:      fromId,
        to:        toId,
        amount,
        createdAt: new Date(),
      }, { session })
    }, {
      readConcern:  { level: "snapshot" },
      writeConcern: { w: "majority" },
      maxCommitTimeMS: 5000,
    })
  } finally {
    await session.endSession()
  }
}

Node.js Driver Patterns

Connect, query and manage collections from Node.js

javascript·Connection and singleton pattern
import { MongoClient, ServerApiVersion } from "mongodb"

const uri = process.env.MONGODB_URI

// Singleton — reuse across requests (critical in serverless)
let client: MongoClient
let clientPromise: Promise<MongoClient>

if (process.env.NODE_ENV === "development") {
  // In dev, use a global to survive HMR
  if (!global._mongoClientPromise) {
    client = new MongoClient(uri)
    global._mongoClientPromise = client.connect()
  }
  clientPromise = global._mongoClientPromise
} else {
  client = new MongoClient(uri, {
    serverApi: {
      version:        ServerApiVersion.v1,
      strict:         true,
      deprecationErrors: true,
    },
  })
  clientPromise = client.connect()
}

export default clientPromise

// Usage in a route
const client = await clientPromise
const db     = client.db("mydb")
const users  = db.collection("users")
javascript·Typed collection and common operations
import { ObjectId, type Collection, type WithId } from "mongodb"

interface User {
  _id?:      ObjectId
  name:      string
  email:     string
  role:      "user" | "admin"
  createdAt: Date
}

async function getUserById(db, id: string): Promise<WithId<User> | null> {
  return db.collection<User>("users").findOne({ _id: new ObjectId(id) })
}

async function createUser(db, data: Omit<User, "_id">) {
  const result = await db.collection<User>("users").insertOne({
    ...data,
    createdAt: new Date(),
  })
  return result.insertedId
}

// Cursor — iterate large result sets without loading all into memory
async function processAllUsers(db) {
  const cursor = db.collection("users").find({ role: "user" })
  for await (const user of cursor) {
    await processUser(user)
  }
}

// Aggregation with typed result
const pipeline = [
  { $match: { role: "user" } },
  { $group: { _id: "$country", count: { $sum: 1 } } },
  { $sort: { count: -1 } },
]
const results = await db.collection("users")
  .aggregate<{ _id: string; count: number }>(pipeline)
  .toArray()

Useful Patterns

Geospatial, full-text search, Change Streams and schema validation

javascript·Geospatial queries
// Document must have a 2dsphere index and GeoJSON location field
// { location: { type: "Point", coordinates: [longitude, latitude] } }

// Near — sort by distance
db.places.find({
  location: {
    $near: {
      $geometry:    { type: "Point", coordinates: [-0.1278, 51.5074] },
      $maxDistance: 5000,   // metres
      $minDistance: 0,
    }
  }
})

// Within a polygon
db.places.find({
  location: {
    $geoWithin: {
      $geometry: {
        type: "Polygon",
        coordinates: [[
          [-0.15, 51.49], [-0.10, 51.49],
          [-0.10, 51.52], [-0.15, 51.52],
          [-0.15, 51.49],
        ]]
      }
    }
  }
})

// $geoNear aggregation stage — includes distance in output
db.places.aggregate([
  {
    $geoNear: {
      near:          { type: "Point", coordinates: [-0.1278, 51.5074] },
      distanceField: "distanceMetres",
      maxDistance:   2000,
      spherical:     true,
      query:         { category: "restaurant" },
    }
  },
  { $limit: 10 },
])
javascript·Full-text search and Change Streams
// Text search (requires text index)
db.posts.find(
  { $text: { $search: "mongodb performance" } },
  { score: { $meta: "textScore" } }
).sort({ score: { $meta: "textScore" } })

// Phrase search and negation
db.posts.find({ $text: { $search: '"window functions" -basic' } })

// Change Streams — react to real-time collection changes
const changeStream = db.collection("orders").watch([
  { $match: { "operationType": { $in: ["insert", "update"] } } },
  { $match: { "fullDocument.status": "pending" } },
])

changeStream.on("change", (change) => {
  console.log("Operation:", change.operationType)
  console.log("Document:",  change.fullDocument)
  console.log("Updated fields:", change.updateDescription?.updatedFields)
})

// Resume after disconnect using resume token
const token = changeStream.resumeToken
const resumed = db.collection("orders").watch([], { resumeAfter: token })
javascript·Schema validation
// Enforce structure at the database level
db.createCollection("users", {
  validator: {
    $jsonSchema: {
      bsonType: "object",
      required: ["name", "email", "role"],
      properties: {
        name: {
          bsonType:    "string",
          minLength:   1,
          maxLength:   100,
          description: "must be a string",
        },
        email: {
          bsonType: "string",
          pattern:  "^[\\w.-]+@[\\w.-]+\\.[a-z]{2,}$",
        },
        role: {
          enum:        ["user", "admin", "moderator"],
          description: "must be one of the allowed values",
        },
        age: {
          bsonType:    "int",
          minimum:     0,
          maximum:     150,
        },
        tags: {
          bsonType: "array",
          items:    { bsonType: "string" },
        },
      },
      additionalProperties: false,
    }
  },
  validationLevel:  "strict",   // "strict" | "moderate"
  validationAction: "error",    // "error" | "warn"
})

// Add / update validator on existing collection
db.runCommand({
  collMod:          "users",
  validator:        { $jsonSchema: { /* ... */ } },
  validationLevel:  "moderate",
})