Toolmingo
Guides10 min read

MongoDB Cheat Sheet: Queries, Aggregation & Patterns

A complete MongoDB cheat sheet — CRUD operations, query operators, aggregation pipeline, indexes, and production patterns. Copy-ready commands for mongosh and Node.js.

MongoDB is a document-oriented NoSQL database that stores data as BSON (Binary JSON). This reference covers every operation you'll need in daily development — from basic CRUD to the aggregation pipeline.

Quick reference

The 25 operations that cover 90% of daily MongoDB work.

Operation mongosh / Driver
db.col.insertOne({}) Insert one document
db.col.insertMany([{},{}]) Insert multiple documents
db.col.findOne({k:v}) Find first match
db.col.find({k:v}) Find all matches
db.col.find({}, {field:1}) Find with projection
db.col.updateOne({q},{$set:{k:v}}) Update first match
db.col.updateMany({q},{$set:{k:v}}) Update all matches
db.col.replaceOne({q},{doc}) Replace entire document
db.col.deleteOne({k:v}) Delete first match
db.col.deleteMany({k:v}) Delete all matches
db.col.countDocuments({q}) Count matching docs
db.col.find().sort({k:1}) Sort ascending
db.col.find().sort({k:-1}) Sort descending
db.col.find().limit(10) Limit results
db.col.find().skip(20).limit(10) Pagination
db.col.createIndex({field:1}) Create index
db.col.dropIndex("name") Drop index
db.col.aggregate([]) Run aggregation pipeline
db.col.distinct("field") Get unique values
db.col.findOneAndUpdate({q},{$set:{}}) Find and update atomically
db.col.bulkWrite([]) Bulk operations
db.createCollection("name") Create collection
db.col.drop() Drop collection
db.dropDatabase() Drop database
show dbs / use mydb List DBs / switch DB

CRUD operations

Insert

// Insert one document — _id is auto-generated if omitted
db.users.insertOne({
  name: "Alice",
  email: "alice@example.com",
  age: 30,
  createdAt: new Date()
})

// Insert many
const result = await db.users.insertMany([
  { name: "Bob", age: 25 },
  { name: "Carol", age: 35 }
])
console.log(result.insertedIds) // { '0': ObjectId('...'), '1': ObjectId('...') }

Find

// Find one — returns null if not found
const user = await db.users.findOne({ email: "alice@example.com" })

// Find all matching — returns a cursor
const cursor = db.users.find({ age: { $gte: 18 } })
const users = await cursor.toArray()

// Projection — 1 = include, 0 = exclude (_id excluded separately)
db.users.find({}, { name: 1, email: 1, _id: 0 })

// Sort + limit + skip (pagination)
db.users.find({})
  .sort({ createdAt: -1 })
  .skip(20)
  .limit(10)

Update

// Update one — $set only changes specified fields
db.users.updateOne(
  { _id: ObjectId("...") },
  { $set: { age: 31, updatedAt: new Date() } }
)

// Update many
db.users.updateMany(
  { status: "inactive" },
  { $set: { status: "archived" } }
)

// Upsert — insert if not found
db.users.updateOne(
  { email: "new@example.com" },
  { $set: { name: "Dave", age: 28 } },
  { upsert: true }
)

// findOneAndUpdate — returns the document (new:true = updated version)
const updated = await db.users.findOneAndUpdate(
  { _id: id },
  { $inc: { loginCount: 1 } },
  { returnDocument: "after" }
)

Delete

// Delete one
db.users.deleteOne({ _id: ObjectId("...") })

// Delete many
db.users.deleteMany({ status: "deleted" })

// findOneAndDelete — returns the deleted document
const deleted = await db.users.findOneAndDelete({ _id: id })

Query operators

Comparison

Operator Meaning Example
$eq Equal { age: { $eq: 30 } }
$ne Not equal { status: { $ne: "banned" } }
$gt Greater than { price: { $gt: 100 } }
$gte Greater than or equal { score: { $gte: 80 } }
$lt Less than { age: { $lt: 18 } }
$lte Less than or equal { rating: { $lte: 3 } }
$in In array { role: { $in: ["admin","mod"] } }
$nin Not in array { tag: { $nin: ["spam"] } }
// Range query
db.products.find({ price: { $gte: 10, $lte: 100 } })

// $in — match any of the values
db.users.find({ role: { $in: ["admin", "editor"] } })

Logical

// $and — all conditions must match (implicit when multiple fields)
db.users.find({ $and: [{ age: { $gte: 18 } }, { status: "active" }] })
// Shorthand (same result):
db.users.find({ age: { $gte: 18 }, status: "active" })

// $or — at least one condition must match
db.users.find({ $or: [{ role: "admin" }, { isSuperUser: true }] })

// $not — negate a condition
db.users.find({ age: { $not: { $lt: 18 } } })

// $nor — none of the conditions match
db.products.find({ $nor: [{ inStock: false }, { price: { $lt: 1 } }] })

Element & evaluation

// $exists — field presence check
db.users.find({ phone: { $exists: true } })
db.users.find({ deletedAt: { $exists: false } })

// $type — BSON type check
db.products.find({ price: { $type: "double" } })
db.users.find({ tags: { $type: "array" } })

// $regex — pattern matching (use indexes for prefix queries)
db.products.find({ name: { $regex: /^laptop/i } })

// $where — JavaScript expression (slow, no index)
db.users.find({ $where: "this.name === this.username" })

Array operators

// $all — array contains all specified elements
db.posts.find({ tags: { $all: ["mongodb", "nodejs"] } })

// $elemMatch — at least one array element matches all conditions
db.orders.find({ items: { $elemMatch: { qty: { $gt: 2 }, price: { $lt: 50 } } } })

// $size — array length equals N
db.posts.find({ tags: { $size: 3 } })

// Query by array index
db.posts.find({ "scores.0": { $gt: 90 } })

Update operators

Operator Effect
$set Set field value
$unset Remove field
$inc Increment numeric field
$mul Multiply numeric field
$min Update if new value is lower
$max Update if new value is higher
$rename Rename field
$push Append to array
$pull Remove from array by value
$addToSet Append to array if not present
$pop Remove first (-1) or last (1) array element
$currentDate Set to current date
// Combine multiple update operators
db.users.updateOne({ _id: id }, {
  $set: { name: "Alice Updated" },
  $inc: { loginCount: 1 },
  $push: { loginHistory: new Date() },
  $currentDate: { updatedAt: true }
})

// $addToSet — no duplicates
db.posts.updateOne({ _id: id }, { $addToSet: { tags: "mongodb" } })

// $pull — remove elements matching a condition
db.users.updateOne({ _id: id }, { $pull: { scores: { $lt: 50 } } })

// Array positional operator $ — update matched array element
db.students.updateOne(
  { _id: id, "grades.subject": "Math" },
  { $set: { "grades.$.score": 95 } }
)

Aggregation pipeline

The aggregation pipeline processes documents through a sequence of stages.

// Basic pipeline structure
db.orders.aggregate([
  { $match: { status: "completed" } },     // Filter documents
  { $group: { _id: "$customerId",          // Group and compute
      total: { $sum: "$amount" },
      count: { $sum: 1 }
    }
  },
  { $sort: { total: -1 } },               // Sort results
  { $limit: 10 }                          // Limit output
])

Common pipeline stages

Stage Purpose
$match Filter documents (put early to use indexes)
$group Group by field and compute aggregations
$project Include/exclude/transform fields
$sort Sort documents
$limit Limit number of documents
$skip Skip N documents
$unwind Flatten array into separate documents
$lookup Left outer join another collection
$addFields Add computed fields
$count Count documents into a field
$facet Multiple aggregations in one pass
$bucket Group into ranges
$out Write results to a collection

Group accumulators

db.sales.aggregate([
  { $group: {
    _id: "$category",
    totalRevenue: { $sum: "$amount" },
    avgAmount:    { $avg: "$amount" },
    maxAmount:    { $max: "$amount" },
    minAmount:    { $min: "$amount" },
    count:        { $sum: 1 },
    products:     { $push: "$productName" },
    uniqueUsers:  { $addToSet: "$userId" },
    firstSale:    { $first: "$date" },
    lastSale:     { $last: "$date" }
  }}
])

$lookup — join collections

// Join orders with users (like SQL LEFT JOIN)
db.orders.aggregate([
  { $lookup: {
    from: "users",
    localField: "userId",
    foreignField: "_id",
    as: "user"
  }},
  { $unwind: "$user" },  // flatten the [user] array
  { $project: {
    orderId: 1,
    amount: 1,
    "user.name": 1,
    "user.email": 1
  }}
])

$unwind — flatten arrays

// Each tag becomes a separate document
db.posts.aggregate([
  { $unwind: "$tags" },
  { $group: { _id: "$tags", count: { $sum: 1 } } },
  { $sort: { count: -1 } }
])

$facet — multiple aggregations in one query

db.products.aggregate([
  { $facet: {
    byCategory: [
      { $group: { _id: "$category", count: { $sum: 1 } } }
    ],
    priceRanges: [
      { $bucket: {
        groupBy: "$price",
        boundaries: [0, 25, 50, 100, 500],
        default: "500+"
      }}
    ],
    totalCount: [
      { $count: "count" }
    ]
  }}
])

Indexes

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

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

// Compound index — order matters for query patterns
db.orders.createIndex({ customerId: 1, createdAt: -1 })

// Text index for full-text search
db.articles.createIndex({ title: "text", body: "text" })
db.articles.find({ $text: { $search: "mongodb tutorial" } })

// TTL index — auto-expire documents
db.sessions.createIndex({ createdAt: 1 }, { expireAfterSeconds: 3600 })

// Partial index — only index documents matching a filter
db.orders.createIndex(
  { customerId: 1 },
  { partialFilterExpression: { status: "active" } }
)

// List and drop indexes
db.users.getIndexes()
db.users.dropIndex("email_1")
db.users.dropIndexes()  // drops all except _id

// Explain query — see if index is used
db.users.find({ email: "alice@example.com" }).explain("executionStats")

Node.js driver patterns

import { MongoClient, ObjectId } from "mongodb"

const client = new MongoClient(process.env.MONGODB_URI)
await client.connect()
const db = client.db("myapp")
const users = db.collection("users")

// Always use ObjectId for _id comparisons
const user = await users.findOne({ _id: new ObjectId("64f2a...") })

// Transactions (replica set or Atlas required)
const session = client.startSession()
try {
  await session.withTransaction(async () => {
    await accounts.updateOne({ _id: fromId }, { $inc: { balance: -amount } }, { session })
    await accounts.updateOne({ _id: toId },   { $inc: { balance: +amount } }, { session })
  })
} finally {
  await session.endSession()
}

// Connection pooling — create client once, reuse across requests
// Next.js / serverless: use global singleton pattern
let cachedClient: MongoClient | null = null
export async function connectDb() {
  if (cachedClient) return cachedClient
  cachedClient = new MongoClient(process.env.MONGODB_URI!)
  await cachedClient.connect()
  return cachedClient
}

mongosh quick commands

# Connect
mongosh "mongodb://localhost:27017"
mongosh "mongodb+srv://user:pass@cluster.mongodb.net/mydb"

# Database and collection management
show dbs
use mydb
show collections
db.stats()

# User management
db.createUser({ user: "app", pwd: "secret", roles: [{ role: "readWrite", db: "mydb" }] })

# Import / export
mongoimport --uri="..." --collection=users --file=users.json --jsonArray
mongoexport --uri="..." --collection=users --out=users.json

# Backup / restore
mongodump --uri="..." --out=./backup
mongorestore --uri="..." ./backup

Common mistakes

Mistake Problem Fix
Forgetting new ObjectId() { _id: "64f2a..." } never matches Always use new ObjectId(id) for _id queries
No index on query field Collection scan on every query createIndex on frequently queried fields
Fetching all fields Sending unused data over the wire Use projection { field: 1 }
$where in production JavaScript eval, can't use indexes Rewrite with proper query operators
Mutating array elements with $set Replaces entire array Use positional $ or $elemMatch
No TTL on session data Sessions grow forever Add TTL index on createdAt
Storing large blobs in documents 16MB document limit hit Use GridFS or external object storage

FAQ

What is the difference between find() and findOne()? find() returns a cursor (lazy, iterate or call .toArray()). findOne() returns a single document or null. Always use findOne() when you expect at most one result.

How do I query a nested field? Use dot notation: db.users.find({ "address.city": "London" }). This works for both objects and arrays.

When should I use $lookup vs embedding documents? Embed when data is read together and doesn't change independently (e.g., post comments under 100). Use $lookup (reference) when data is large, shared across documents, or updated independently (e.g., user profile referenced by many orders).

How do I do a case-insensitive search? Use $regex with the i flag: { name: { $regex: /alice/i } }. For full-text search across many documents, create a text index and use $text: { $search: "..." } instead.

How do I handle MongoDB _id in JSON APIs? ObjectId is not a plain string. When serializing to JSON, call .toString() or use a library like mongoose which handles this automatically. In REST APIs, accept the string ID from the client and convert with new ObjectId(idString).

What is the 16MB document limit? Each MongoDB document can be at most 16MB. For large binary files (images, videos), use GridFS which splits files into 255KB chunks, or store files in object storage (S3) and only store the URL in MongoDB.

Related tools

Keep reading

All Toolmingotools are free & run in your browser

No sign-up, no upload, no watermark. Your files never leave your device.

Browse all tools