MongoDB
mongosh Basics
# Connect
mongosh # localhost:27017
mongosh "mongodb://localhost:27017/mydb"
mongosh "mongodb+srv://user:pass@cluster.mongodb.net/mydb"
mongosh --host rs0/host1:27017,host2:27017 --authenticationDatabase admin -u admin -p
show dbs // list databases
use mydb // switch database (creates on first write)
show collections // list collections in current db
db.stats() // database stats
db.collection.stats() // collection stats
db.getCollectionNames() // programmatic collection list
db.dropDatabase() // drop current database
db.createCollection("logs", { capped: true, size: 1048576, max: 1000 })
CRUD Operations
Insert
db.users.insertOne({ name: "Alice", email: "alice@example.com", age: 30 })
db.users.insertMany([
{ name: "Bob", email: "bob@example.com", age: 25 },
{ name: "Carol", email: "carol@example.com", age: 35 }
])
// insertMany with ordered: false continues on error
db.users.insertMany(docs, { ordered: false })
Find
db.users.find() // all documents
db.users.find({ age: { $gt: 25 } }) // age > 25
db.users.findOne({ email: "alice@example.com" }) // first match
db.users.find({ status: "active" }, { name: 1, email: 1, _id: 0 }) // projection
db.users.find().sort({ age: -1 }).limit(10).skip(20) // sort, paginate
db.users.countDocuments({ status: "active" })
db.users.distinct("status")
Update
db.users.updateOne(
{ email: "alice@example.com" },
{ $set: { age: 31, updatedAt: new Date() } }
)
db.users.updateMany(
{ status: "inactive" },
{ $set: { archived: true }, $currentDate: { updatedAt: true } }
)
// Upsert: insert if not found
db.users.updateOne(
{ email: "dave@example.com" },
{ $set: { name: "Dave", age: 28 } },
{ upsert: true }
)
db.users.replaceOne(
{ _id: ObjectId("...") },
{ name: "Alice", email: "alice@new.com", age: 31 }
)
Delete
db.users.deleteOne({ email: "bob@example.com" })
db.users.deleteMany({ status: "inactive", lastLogin: { $lt: ISODate("2024-01-01") } })
db.users.findOneAndDelete({ email: "bob@example.com" }) // returns deleted doc
Query Operators
Comparison and Logical
{ age: { $eq: 30 } } // equals (same as { age: 30 })
{ age: { $gt: 25, $lte: 40 } } // range
{ status: { $in: ["active", "pending"] } }
{ status: { $nin: ["banned"] } }
{ $and: [{ age: { $gte: 18 } }, { status: "active" }] }
{ $or: [{ role: "admin" }, { age: { $gte: 21 } }] }
{ age: { $not: { $gt: 65 } } }
Element and Evaluation
{ phone: { $exists: true } } // field exists
{ age: { $type: "number" } } // BSON type check
{ name: { $regex: /^alice/i } } // regex match
{ bio: { $regex: "engineer", $options: "i" } }
{ score: { $mod: [10, 0] } } // score divisible by 10
Array Operators
{ tags: { $all: ["mongodb", "nosql"] } } // array contains all
{ tags: { $size: 3 } } // array has exactly 3 elements
{ results: { $elemMatch: { score: { $gt: 90 }, subject: "math" } } }
{ "scores.0": { $gt: 80 } } // first element > 80
Update Operators
// Field operators
{ $set: { status: "active" } }
{ $unset: { tempField: "" } } // remove field
{ $inc: { views: 1, score: -5 } } // increment/decrement
{ $rename: { "old_field": "new_field" } }
{ $min: { lowScore: 50 } } // update if new value is lower
{ $max: { highScore: 100 } } // update if new value is higher
{ $mul: { price: 1.1 } } // multiply by 1.1
// Array operators
{ $push: { tags: "new-tag" } }
{ $push: { scores: { $each: [90, 85], $sort: -1, $slice: 10 } } } // push, sort, keep top 10
{ $addToSet: { tags: "unique-tag" } } // add only if not present
{ $pull: { tags: "old-tag" } } // remove matching elements
{ $pull: { results: { score: { $lt: 50 } } } } // remove by condition
{ $pop: { queue: -1 } } // remove first element (1 for last)
Aggregation Pipeline
db.orders.aggregate([
{ $match: { status: "completed", createdAt: { $gte: ISODate("2025-01-01") } } },
{ $group: {
_id: "$customerId",
totalSpent: { $sum: "$amount" },
orderCount: { $sum: 1 },
avgOrder: { $avg: "$amount" },
lastOrder: { $max: "$createdAt" }
}},
{ $sort: { totalSpent: -1 } },
{ $limit: 10 },
{ $project: {
customerId: "$_id",
totalSpent: 1,
orderCount: 1,
avgOrder: { $round: ["$avgOrder", 2] },
_id: 0
}}
])
$lookup (Join)
db.orders.aggregate([
{ $lookup: {
from: "users",
localField: "customerId",
foreignField: "_id",
as: "customer"
}},
{ $unwind: "$customer" }, // flatten single-element array
{ $project: { amount: 1, "customer.name": 1, "customer.email": 1 } }
])
// Pipeline lookup (correlated subquery)
db.orders.aggregate([
{ $lookup: {
from: "products",
let: { productIds: "$items.productId" },
pipeline: [
{ $match: { $expr: { $in: ["$_id", "$$productIds"] } } },
{ $project: { name: 1, price: 1 } }
],
as: "productDetails"
}}
])
$facet (Multiple Aggregations in One Pass)
db.products.aggregate([
{ $facet: {
priceRanges: [
{ $bucket: { groupBy: "$price", boundaries: [0, 25, 50, 100, Infinity] } }
],
topRated: [
{ $sort: { rating: -1 } },
{ $limit: 5 },
{ $project: { name: 1, rating: 1 } }
],
totalCount: [
{ $count: "count" }
]
}}
])
Indexing
// Single field
db.users.createIndex({ email: 1 }) // ascending
db.users.createIndex({ email: 1 }, { unique: true }) // unique
// Compound (order matters: equality, sort, range)
db.orders.createIndex({ customerId: 1, createdAt: -1 })
// Multikey (automatically indexes array elements)
db.articles.createIndex({ tags: 1 })
// Text index (one per collection)
db.articles.createIndex({ title: "text", body: "text" })
db.articles.find({ $text: { $search: "mongodb aggregation" } })
// TTL (auto-delete documents after expiry)
db.sessions.createIndex({ createdAt: 1 }, { expireAfterSeconds: 3600 })
// Partial (index subset of documents)
db.orders.createIndex(
{ createdAt: -1 },
{ partialFilterExpression: { status: "active" } }
)
// Wildcard (index all fields or subfields in a document)
db.logs.createIndex({ "metadata.$**": 1 })
// List and manage indexes
db.users.getIndexes()
db.users.dropIndex("email_1")
db.users.dropIndexes() // drops all non-_id indexes
Schema Design Patterns
Embedding vs Referencing
// Embedding: data accessed together, 1:few relationship
// Good for: addresses in user doc, comments on a blog post (bounded)
{
_id: ObjectId("..."),
name: "Alice",
addresses: [
{ type: "home", street: "123 Main St", city: "Springfield" },
{ type: "work", street: "456 Corp Ave", city: "Shelbyville" }
]
}
// Referencing: data accessed independently, 1:many or many:many
// Good for: orders referencing products, users referencing groups
{
_id: ObjectId("..."),
customerId: ObjectId("..."), // reference to users collection
items: [
{ productId: ObjectId("..."), qty: 2, price: 29.99 }
]
}
Denormalization
Store computed or copied fields to avoid joins:
// Store author name directly on posts (update on author rename)
{ title: "My Post", authorId: ObjectId("..."), authorName: "Alice" }
Polymorphic Pattern
Single collection, different shapes distinguished by a type field:
// notifications collection
{ type: "email", to: "alice@example.com", subject: "Welcome", body: "..." }
{ type: "sms", to: "+1234567890", message: "Your code is 1234" }
{ type: "push", deviceToken: "abc...", title: "New message", payload: { ... } }
Mongoose ODM (Node.js)
Schema and Model
const mongoose = require("mongoose");
await mongoose.connect("mongodb://localhost:27017/mydb");
const userSchema = new mongoose.Schema({
name: { type: String, required: true, trim: true },
email: { type: String, required: true, unique: true, lowercase: true },
age: { type: Number, min: 0 },
role: { type: String, enum: ["user", "admin"], default: "user" },
profile: {
bio: String,
avatar: String
},
tags: [String],
createdAt: { type: Date, default: Date.now }
});
// Virtuals
userSchema.virtual("isAdmin").get(function () {
return this.role === "admin";
});
// Instance methods
userSchema.methods.toPublic = function () {
const { _id, name, email, role } = this.toObject();
return { id: _id, name, email, role };
};
// Static methods
userSchema.statics.findByEmail = function (email) {
return this.findOne({ email: email.toLowerCase() });
};
// Middleware (hooks)
userSchema.pre("save", function (next) {
this.updatedAt = new Date();
next();
});
const User = mongoose.model("User", userSchema);
Queries with Mongoose
const users = await User.find({ role: "admin" }).sort({ name: 1 }).limit(10).lean();
const user = await User.findById(id).select("name email");
await User.findOneAndUpdate({ email }, { $inc: { loginCount: 1 } }, { new: true });
await User.deleteMany({ lastLogin: { $lt: cutoffDate } });
Populate (Reference Resolution)
const postSchema = new mongoose.Schema({
title: String,
author: { type: mongoose.Schema.Types.ObjectId, ref: "User" }
});
const Post = mongoose.model("Post", postSchema);
const posts = await Post.find().populate("author", "name email").lean();
// Nested populate
const posts = await Post.find().populate({ path: "comments", populate: { path: "user" } });
lean()
lean() returns plain JS objects instead of Mongoose documents. Use for read-only queries -- skips hydration, significantly faster.
Transactions
const session = await mongoose.startSession();
session.startTransaction();
try {
await Account.updateOne({ _id: from }, { $inc: { balance: -amount } }, { session });
await Account.updateOne({ _id: to }, { $inc: { balance: amount } }, { session });
await session.commitTransaction();
} catch (err) {
await session.abortTransaction();
throw err;
} finally {
session.endSession();
}
Transactions require a replica set (or sharded cluster). For local dev, start mongod with --replSet rs0 and run rs.initiate().
MongoDB Atlas
Connection
# Connection string from Atlas dashboard
mongosh "mongodb+srv://cluster0.abc123.mongodb.net/mydb" --apiVersion 1 --username admin
# In application code
mongoose.connect("mongodb+srv://admin:password@cluster0.abc123.mongodb.net/mydb?retryWrites=true&w=majority")
Key Settings
- Network Access: whitelist IP addresses or use
0.0.0.0/0for dev (not production). - Database Access: create users with specific roles (readWrite, atlasAdmin).
- Backups: Atlas provides continuous backups and point-in-time restore for M10+ clusters. Snapshots can be downloaded or restored to a new cluster.
Performance
explain()
db.orders.find({ customerId: ObjectId("...") }).explain("executionStats")
Key fields: totalDocsExamined vs nReturned (ratio should be close to 1:1), executionTimeMillis, winningPlan.stage (IXSCAN good, COLLSCAN bad), indexBounds.
Database Profiler
db.setProfilingLevel(1, { slowms: 100 }) // log queries slower than 100ms
db.system.profile.find().sort({ ts: -1 }).limit(5)
db.setProfilingLevel(0) // disable profiler
Index Hints
db.orders.find({ status: "active" }).hint({ status: 1, createdAt: -1 })
db.orders.find({ status: "active" }).hint("status_1_createdAt_-1")
Backup and Restore
# Full database dump
mongodump --uri="mongodb://localhost:27017/mydb" --out=/backup/$(date +%F)
# Specific collection
mongodump --uri="mongodb://localhost:27017/mydb" --collection=users --out=/backup
# Compressed dump
mongodump --uri="mongodb://localhost:27017/mydb" --gzip --archive=backup.gz
# Restore full database
mongorestore --uri="mongodb://localhost:27017" /backup/2025-04-13/
# Restore specific collection
mongorestore --uri="mongodb://localhost:27017/mydb" --collection=users /backup/mydb/users.bson
# Restore from compressed archive
mongorestore --uri="mongodb://localhost:27017" --gzip --archive=backup.gz
# Drop existing data before restoring
mongorestore --drop --uri="mongodb://localhost:27017" /backup/2025-04-13/
Replica Sets
Setup
# Start three mongod instances
mongod --replSet rs0 --port 27017 --dbpath /data/rs0-0
mongod --replSet rs0 --port 27018 --dbpath /data/rs0-1
mongod --replSet rs0 --port 27019 --dbpath /data/rs0-2
// Initiate from mongosh connected to one node
rs.initiate({
_id: "rs0",
members: [
{ _id: 0, host: "localhost:27017" },
{ _id: 1, host: "localhost:27018" },
{ _id: 2, host: "localhost:27019" }
]
})
rs.status()
rs.conf()
Read Preference and Write Concern
// Read preference: where reads go
// primary (default), primaryPreferred, secondary, secondaryPreferred, nearest
db.users.find().readPref("secondaryPreferred")
// In connection string
"mongodb://host1,host2,host3/mydb?replicaSet=rs0&readPreference=secondaryPreferred"
// Write concern: how many nodes acknowledge a write
db.users.insertOne({ name: "Alice" }, { writeConcern: { w: "majority", wtimeout: 5000 } })
Change Streams
// Watch a collection for real-time changes
const changeStream = db.collection("orders").watch();
changeStream.on("change", (change) => {
console.log(change.operationType, change.fullDocument);
});
// With filters
const pipeline = [{ $match: { "fullDocument.status": "shipped" } }];
const changeStream = db.collection("orders").watch(pipeline, { fullDocument: "updateLookup" });
// Resume after disconnect
const changeStream = db.collection("orders").watch([], { resumeAfter: lastResumeToken });
// Mongoose change streams
const stream = Order.watch();
stream.on("change", (data) => { /* handle */ });
Requires replica set or sharded cluster. Use fullDocument: "updateLookup" to include the full document on update events.
Common Patterns
Cursor-Based Pagination
// More efficient than skip/limit for large datasets
const pageSize = 20;
// First page
const firstPage = await db.orders.find().sort({ _id: -1 }).limit(pageSize).toArray();
// Next page: use last _id as cursor
const lastId = firstPage[firstPage.length - 1]._id;
const nextPage = await db.orders.find({ _id: { $lt: lastId } }).sort({ _id: -1 }).limit(pageSize).toArray();
Full-Text Search
db.articles.createIndex({ title: "text", body: "text" });
db.articles.find(
{ $text: { $search: "mongodb performance" } },
{ score: { $meta: "textScore" } }
).sort({ score: { $meta: "textScore" } })
// Atlas Search (more powerful, uses Lucene)
db.articles.aggregate([
{ $search: { index: "default", text: { query: "mongodb performance", path: ["title", "body"] } } },
{ $project: { title: 1, score: { $meta: "searchScore" } } }
])
Geospatial Queries
// Store GeoJSON point
db.places.insertOne({
name: "Central Park",
location: { type: "Point", coordinates: [-73.965, 40.782] } // [lng, lat]
})
db.places.createIndex({ location: "2dsphere" })
// Find within radius (meters)
db.places.find({
location: { $nearSphere: { $geometry: { type: "Point", coordinates: [-73.97, 40.77] }, $maxDistance: 2000 } }
})
// Find within polygon
db.places.find({
location: { $geoWithin: { $geometry: { type: "Polygon", coordinates: [[[...], ...]] } } }
})