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.