⏳
Loading cheatsheet...
CRUD, Query Operators, Aggregation Pipeline, Indexes, Schema Design, Replication, Sharding, Mongoose ODM — database mastery.
// ── Insert ──
db.users.insertOne({ name: "Alice", age: 30, email: "alice@example.com" })
db.users.insertMany([
{ name: "Bob", age: 25, role: "developer" },
{ name: "Carol", age: 28, role: "designer" },
{ name: "Dave", age: 35, role: "manager" },
])
// ── Read (Find) ──
db.users.find() // all documents
db.users.find({ role: "developer" }) // equality
db.users.find({ age: { $gt: 25 } }) // age > 25
db.users.find({ age: { $gte: 25, $lte: 35 } }) // 25 <= age <= 35
db.users.find({ role: { $in: ["developer", "designer"] } })
db.users.find({ name: { $ne: "Alice" } })
db.users.find({ email: { $exists: true } })
db.users.find({ tags: { $all: ["react", "node"] } })
db.users.find({ name: /alice/i }) // regex (case-insensitive)
db.users.find({ $or: [{ age: { $lt: 20 } }, { role: "intern" }] })
// ── Projection (select fields) ──
db.users.find({}, { name: 1, age: 1, _id: 0 }) // include only name, age
db.users.find({}, { password: 0 }) // exclude password
// ── Sort, Skip, Limit ──
db.users.find().sort({ age: -1 }) // descending
db.users.find().sort({ name: 1, age: -1 }) // compound sort
db.users.find().skip(10).limit(10) // pagination (page 2)
// ── Update ──
db.users.updateOne({ name: "Alice" }, { $set: { age: 31 } })
db.users.updateMany({ role: "developer" }, { $inc: { age: 1 } })
db.users.updateOne({ name: "Alice" }, {
$set: { email: "new@example.com" },
$unset: { tempField: "" },
$rename: { oldName: "newName" },
})
db.users.updateOne({ name: "Alice" }, { $push: { tags: "mongodb" } })
db.users.updateOne({ name: "Alice" }, { $addToSet: { tags: "node" } })
db.users.updateOne({ name: "Alice" }, { $pull: { tags: "legacy" } })
db.users.updateOne({ name: "Alice" }, { $pullAll: { tags: ["a", "b"] } })
// ── Delete ──
db.users.deleteOne({ name: "Bob" })
db.users.deleteMany({ status: "inactive" })
// ── Upsert (insert if not found) ──
db.users.updateOne(
{ email: "new@example.com" },
{ $set: { name: "New User", email: "new@example.com" } },
{ upsert: true }
)
// ── findOneAndUpdate / findOneAndDelete ──
db.users.findOneAndUpdate(
{ name: "Alice" },
{ $set: { age: 32 } },
{ returnNewDocument: true } // return updated doc (mongosh)
)| Operator | Meaning | Example |
|---|---|---|
| $eq / implicit | Equal | { age: 25 } |
| $ne | Not equal | { age: { $ne: 25 } } |
| $gt / $gte | Greater than / or equal | { age: { $gt: 25 } } |
| $lt / $lte | Less than / or equal | { age: { $lt: 30 } } |
| $in | Match any in array | { role: { $in: ["a","b"] } } |
| $nin | Match none in array | { role: { $nin: ["c"] } } |
| $exists | Field exists | { email: { $exists: true } } |
| $type | BSON type | { age: { $type: "number" } } |
| Operator | Meaning |
|---|---|
| $all | Contains all specified elements |
| $elemMatch | Match element by multiple conditions |
| $size | Array has specified length |
| $push | Add element to array |
| $addToSet | Add if not already present |
| $pull | Remove matching elements |
| $pullAll | Remove all specified elements |
| $pop | Remove first (-1) or last (1) |
| $each | Used with $push for multiple values |
| $slice | Project subset of array elements |
// ── Basic Pipeline ──
db.orders.aggregate([
// Stage 1: Filter
{ $match: { status: "completed", date: { $gte: ISODate("2024-01-01") } } },
// Stage 2: Group
{ $group: {
_id: "$customerId",
totalSpent: { $sum: "$amount" },
orderCount: { $sum: 1 },
avgOrder: { $avg: "$amount" },
lastOrder: { $max: "$date" },
}},
// Stage 3: Sort
{ $sort: { totalSpent: -1 } },
// Stage 4: Limit
{ $limit: 10 },
])
// ── $lookup (Left Join) ──
db.orders.aggregate([
{ $match: { status: "completed" } },
{ $lookup: {
from: "users",
localField: "customerId",
foreignField: "_id",
as: "customer"
}},
{ $unwind: "$customer" },
{ $project: {
orderId: "$_id",
customerName: "$customer.name",
amount: 1,
date: 1,
}}
])
// ── $unwind + $group for array analysis ──
db.articles.aggregate([
{ $unwind: "$tags" },
{ $group: {
_id: "$tags",
articleCount: { $sum: 1 },
avgViews: { $avg: "$views" },
}},
{ $sort: { articleCount: -1 } },
])
// ── $facet (multiple pipelines in one query) ──
db.products.aggregate([
{ $match: { category: "electronics" } },
{ $facet: {
priceStats: [
{ $group: { _id: null, avg: { $avg: "$price" }, max: { $max: "$price" } } }
],
topRated: [
{ $sort: { rating: -1 } },
{ $limit: 5 },
],
byBrand: [
{ $group: { _id: "$brand", count: { $sum: 1 } } },
{ $sort: { count: -1 } },
],
}}
])// ── $bucket (categorize into ranges) ──
db.users.aggregate([
{ $bucket: {
groupBy: "$age",
boundaries: [0, 18, 25, 35, 50, 100],
default: "50+",
output: { count: { $sum: 1 } }
}}
])
// ── $setWindowFields (window functions, MongoDB 5.0+) ──
db.sales.aggregate([
{ $sort: { date: 1 } },
{ $setWindowFields: {
partitionBy: "$storeId",
sortBy: { date: 1 },
output: {
runningTotal: {
$sum: "$amount",
window: { documents: ["unbounded", "current"] }
},
prevAmount: {
$shift: { output: "$amount", by: -1 }
},
rank: {
$rank: {},
window: { documents: ["unbounded", "unbounded"] }
},
}
}}
])
// ── $graphLookup (recursive join, e.g. org chart) ──
db.employees.aggregate([
{ $match: { name: "CEO" } },
{ $graphLookup: {
from: "employees",
startWith: "$_id",
connectFromField: "managerId",
connectToField: "_id",
as: "reports"
}}
])| Stage | Purpose |
|---|---|
| $match | Filter documents (like WHERE) |
| $group | Group by field and compute aggregates |
| $project | Reshape documents (include/exclude/transform fields) |
| $sort | Sort documents by one or more fields |
| $limit / $skip | Pagination (limit results, skip N docs) |
| $unwind | Deconstruct array field into multiple docs |
| $lookup | Left join with another collection |
| $facet | Run multiple pipelines in parallel on same docs |
| $addFields | Add new fields or overwrite existing ones |
| $set | Alias for $addFields |
| $count | Count documents passing through stage |
| $out / $merge | Write results to a collection |
| $bucket | Categorize into predefined ranges |
| $graphLookup | Recursive multi-level join (e.g. tree) |
| $setWindowFields | Window functions (running totals, rank) |
| $replaceRoot | Replace document with embedded sub-document |
$match early to reduce documents flowing through the pipeline. Add indexes on $match and $sort fields. Use $project early to reduce document size. Use allowDiskUse: true for large datasets that exceed memory limits.// ── Create Indexes ──
db.users.createIndex({ email: 1 }) // single field
db.users.createIndex({ email: 1 }, { unique: true }) // unique
db.users.createIndex({ name: 1, age: -1 }) // compound
db.users.createIndex({ "address.city": 1 }) // embedded field
db.users.createIndex({ tags: 1 }) // multikey (array)
// ── Special Indexes ──
db.users.createIndex({ name: "text" }) // text index
db.users.createIndex({ loc: "2dsphere" }) // geospatial
db.users.createIndex(
{ "$**": 1 }, // wildcard
{ wildcardProjection: { "metadata.*": 1 } }
)
// ── TTL Index (auto-delete documents) ──
db.sessions.createIndex({ createdAt: 1 }, { expireAfterSeconds: 3600 })
// ── Partial Index (index only matching docs) ──
db.orders.createIndex(
{ status: 1, createdAt: -1 },
{ partialFilterExpression: { status: "active" } }
)
// ── Background Index Creation ──
db.users.createIndex({ email: 1 }, { background: true })
// ── Manage Indexes ──
db.users.getIndexes()
db.users.dropIndex("email_1")
db.users.dropIndexes() // drop all (except _id)
// ── Explain Query Plan ──
db.users.find({ email: "alice@example.com" }).explain("executionStats")
db.users.find({ age: { $gt: 25 } }).sort({ name: 1 }).explain()
// ── Covered Query (index-only scan) ──
// If the index contains all queried fields, MongoDB
// returns results directly from the index (no doc lookup)
db.users.find({ status: "active" }, { _id: 0, status: 1, name: 1 })
.sort({ name: 1 })
// Requires index on { status: 1, name: 1 }| Type | Use Case |
|---|---|
| Single Field | Queries on one field |
| Compound | Queries on multiple fields (ESR rule) |
| Multikey | Queries on array elements |
| Text | Full-text search on string content |
| Geospatial (2dsphere) | Location-based queries |
| Unique | Enforce uniqueness constraint |
| Sparse | Index only docs where field exists |
| Partial | Index only docs matching filter |
| TTL | Auto-expire documents by date |
| Wildcard | Index arbitrary fields |
| Hashed | Hash-based sharding key |
| Position | Type | Example |
|---|---|---|
| 1st (E) | Equality | { status: "active" } |
| 2nd (S) | Sort | .sort({ createdAt: -1 }) |
| 3rd (R) | Range | { age: { $gt: 25 } } |
{ status: 1, createdAt: -1, age: 1 }db.users.aggregate([{ "$indexStats": {} }]) and remove unused indexes.// ── Schema Validation (MongoDB 3.6+) ──
db.createCollection("users", {
validator: {
$jsonSchema: {
bsonType: "object",
required: ["name", "email", "role"],
properties: {
name: { bsonType: "string", minLength: 2, maxLength: 100 },
email: { bsonType: "string", pattern: "^.+@.+$" },
age: { bsonType: "int", minimum: 0, maximum: 150 },
role: { enum: ["user", "admin", "moderator"] },
tags: {
bsonType: "array",
items: { bsonType: "string" },
maxItems: 20,
},
createdAt: { bsonType: "date" },
},
additionalProperties: false,
}
},
validationAction: "error", // "error" or "warn"
validationLevel: "strict", // "strict" or "moderate"
})
// ── Embedding vs Referencing ──
// EMBEDDING (denormalized): Good for 1:1, 1:few, read-heavy
db.users.insertOne({
name: "Alice",
email: "alice@example.com",
profile: {
bio: "Developer",
website: "https://alice.dev",
},
address: {
street: "123 Main St",
city: "San Francisco",
state: "CA",
zip: "94102",
},
})
// REFERENCING (normalized): Good for 1:many, many:many
db.users.insertOne({ name: "Alice", email: "alice@example.com" })
db.posts.insertOne({
title: "My Post",
authorId: ObjectId("..."), // reference to user
tags: ["mongodb", "schema"],
comments: [ // sub-document with refs
{ userId: ObjectId("..."), text: "Great post!", createdAt: new Date() },
],
})
// ── One-to-Many: Embedding Pattern ──
db.products.insertOne({
name: "Laptop",
price: 999,
reviews: [
{ userId: ObjectId("..."), rating: 5, text: "Excellent!", date: new Date() },
{ userId: ObjectId("..."), rating: 4, text: "Good value", date: new Date() },
],
})
// Use when: few items (usually < 100), items always read with parent
// ── One-to-Many: Referencing Pattern (many items) ──
db.products.insertOne({ name: "Laptop", price: 999 })
db.reviews.insertMany([
{ productId: ObjectId("..."), rating: 5, text: "Excellent!" },
{ productId: ObjectId("..."), rating: 4, text: "Good value" },
// ... thousands of reviews
])
// Use when: many items, items accessed independently, unlimited growth| Factor | Embed | Reference |
|---|---|---|
| Relationship | 1:1, 1:few | 1:many, many:many |
| Data growth | Bounded (no unlimited arrays) | Unlimited growth |
| Read pattern | Always read together | Independent access |
| Update pattern | Rare updates to sub-docs | Frequent independent updates |
| Performance | One query (fast reads) | $lookup or app-level join |
| Consistency | Atomic (single doc) | Manual/transactions |
| Document size | Must stay under 16MB | No size concern |
| Pattern | Description |
|---|---|
| Polymorphic | { itemType: "photo", item: { ... } } |
| Bucket | Store time-series in buckets (per hour/day) |
| Outlier | Move large arrays to separate collection |
| Tree (Materialized Path) | { path: "/category/sub/item" } |
| Tree (Nested Set) | { lft: 1, rgt: 6 } for nested categories |
| Attribute | { attributes: { color: "red", size: "L" } } |
| Schema Versioning | { __v: 2, ... } track schema migrations |
// ── Transactions (requires replica set, MongoDB 4.0+) ──
const session = client.startSession()
session.startTransaction()
try {
await db.accounts.updateOne(
{ _id: fromAccount },
{ $inc: { balance: -amount } },
{ session }
)
await db.accounts.updateOne(
{ _id: toAccount },
{ $inc: { balance: amount } },
{ session }
)
await session.commitTransaction()
} catch (error) {
await session.abortTransaction()
throw error
} finally {
session.endSession()
}
// ── With Retry Logic ──
const runTransaction = async (session, fn) => {
try {
await fn(session)
await session.commitTransaction()
} catch (error) {
await session.abortTransaction()
if (error.hasErrorLabel('TransientTransactionError')) {
console.log('Retrying transaction...')
return runTransaction(client.startSession(), fn)
}
throw error
}
}
// ── Replica Set Configuration ──
// mongod --replSet rs0 --port 27017
rs.initiate({
_id: "rs0",
members: [
{ _id: 0, host: "mongo1:27017", priority: 2 }, // primary
{ _id: 1, host: "mongo2:27017" }, // secondary
{ _id: 2, host: "mongo3:27017", arbiterOnly: true }, // arbiter
],
settings: {
electionTimeoutMillis: 10000,
writeConcernMajorityJournalDefault: true,
}
})
// ── Read Preferences ──
db.users.find().readPref("primary") // default, strongest consistency
db.users.find().readPref("primaryPreferred") // primary, fallback to secondary
db.users.find().readPref("secondary") // any secondary
db.users.find().readPref("secondaryPreferred")
db.users.find().readPref("nearest") // lowest latency member
// ── Write Concern ──
db.orders.insertOne(doc, { writeConcern: { w: "majority", j: true, wtimeout: 5000 } })
// w: number of nodes, "majority", or "majority"
// j: journal (durability guarantee)
// wtimeout: timeout in ms| Member Type | Role | Votes |
|---|---|---|
| Primary | Accepts all writes, reads by default | Yes |
| Secondary | Replicates data, can serve reads | Yes |
| Arbiter | No data, only votes in elections | Yes |
| Hidden | Replicates data, invisible to app | Yes (configurable) |
| Delayed | Secondary with time lag (backup) | Yes (configurable) |
| Priority 0 | Never becomes primary | Yes |
| Level | Meaning |
|---|---|
| w: 1 | Primary acknowledged |
| w: 0 | Fire and forget (no ack) |
| w: "majority" | Majority of replicas acknowledged |
| w: N | N specific nodes acknowledged |
| j: true | Write written to journal (on-disk) |
| wtimeout: 5000 | Timeout after 5 seconds |
writeConcern: { w: 'majority' } for strong consistency, but be aware of the latency trade-off. For single-document operations, atomicity is guaranteed without transactions.const mongoose = require('mongoose');
const { Schema, model } = mongoose;
// ── Define Schema ──
const userSchema = new Schema({
name: { type: String, required: true, trim: true, minlength: 2, maxlength: 100 },
email: {
type: String, required: true, unique: true, lowercase: true, trim: true,
match: [/^\S+@\S+\.\S+$/, 'Invalid email format'],
},
password: { type: String, required: true, minlength: 8, select: false },
age: { type: Number, min: 0, max: 150, default: null },
role: { type: String, enum: ['user', 'admin', 'moderator'], default: 'user' },
tags: [{ type: String }],
profile: {
bio: String,
website: String,
avatar: String,
},
isActive: { type: Boolean, default: true },
lastLogin: Date,
}, {
timestamps: true, // adds createdAt, updatedAt
toJSON: { virtuals: true },
toObject: { virtuals: true },
});
// ── Virtuals (computed fields) ──
userSchema.virtual('fullName').get(function () {
return `${this.firstName} ${this.lastName}`;
});
// ── Instance Methods ──
userSchema.methods.comparePassword = async function (candidate) {
return bcrypt.compare(candidate, this.password);
};
// ── Static Methods ──
userSchema.statics.findByEmail = function (email) {
return this.findOne({ email });
};
// ── Pre/Post Hooks ──
userSchema.pre('save', async function (next) {
if (!this.isModified('password')) return next();
this.password = await bcrypt.hash(this.password, 12);
next();
});
userSchema.post('save', function (doc) {
console.log('User saved:', doc._id);
});
// ── Indexes ──
userSchema.index({ email: 1 }, { unique: true });
userSchema.index({ name: 'text' });
const User = model('User', userSchema);
module.exports = User;// ── CRUD with Mongoose ──
// Create
const user = await User.create({ name: "Alice", email: "alice@ex.com", password: "secret" });
// Read
const user = await User.findById(id).select('-password');
const users = await User.find({ role: 'admin' }).sort({ createdAt: -1 }).limit(10);
const user = await User.findOne({ email: 'alice@example.com' });
// Update
const user = await User.findByIdAndUpdate(id, { age: 31 }, { new: true, runValidators: true });
await User.updateMany({ role: 'user' }, { $set: { isActive: true } });
// Delete
await User.findByIdAndDelete(id);
await User.deleteMany({ isActive: false });
// ── Pagination Helper ──
async function getUsers(page = 1, limit = 10, filter = {}) {
const skip = (page - 1) * limit;
const [docs, total] = await Promise.all([
User.find(filter).sort({ createdAt: -1 }).skip(skip).limit(limit),
User.countDocuments(filter),
]);
return { docs, total, page, totalPages: Math.ceil(total / limit) };
}
// ── Aggregation with Mongoose ──
const stats = await User.aggregate([
{ $group: { _id: "$role", count: { $sum: 1 } } },
]);
// ── Connect to MongoDB ──
mongoose.connect(process.env.MONGODB_URI, {
dbName: 'myapp',
})
.then(() => console.log('Connected to MongoDB'))
.catch((err) => console.error('Connection error:', err));select: false for sensitive fields like passwords. Use .select('-password') when querying to exclude them from results. Add timestamps: true to every schema for automatic createdAt/updatedAt tracking.// ── Profiling ──
db.setProfilingLevel(1, { slowms: 50 }) // log queries > 50ms
db.system.profile.find().sort({ ts: -1 }).limit(10)
db.setProfilingLevel(0) // disable
// ── Server Status ──
db.serverStatus()
db.serverStatus().connections
db.serverStatus().opLatencies
// ── Collection Stats ──
db.users.stats()
db.users.stats({ scale: 1024 * 1024 }) // in MB
// ── Current Operations ──
db.currentOp(true) // all operations
db.currentOp({ "op": "query", "command.find": "users" })
// ── Kill Long-Running Operation ──
db.currentOp({ "secs_running": { $gt: 30 } }).forEach(op => {
db.killOp(op.opid)
})
// ── Memory Usage ──
db.users.totalIndexSize() // total index size in bytes
db.users.totalSize() // total data + index size
db.users.storageSize() // data size (compressed)
// ── Sharding ──
sh.enableSharding("myapp")
sh.shardCollection("myapp.orders", { customerId: "hashed" })
// Shard key strategies:
// 1. Hashed: evenly distributed writes (best for high write throughput)
// 2. Range: enables range queries on shard key
// ── Change Streams (real-time data changes) ──
const changeStream = db.orders.watch([
{ $match: { "fullDocument.status": "completed" } },
]);
changeStream.on("change", (event) => {
console.log("Order completed:", event.fullDocument);
});
// ── Connection Pool Configuration (Node.js) ──
mongoose.connect(uri, {
maxPoolSize: 100, // max connections in pool
minPoolSize: 5, // min connections maintained
socketTimeoutMS: 45000, // socket timeout
serverSelectionTimeoutMS: 5000,
heartbeatFrequencyMS: 10000,
});| Check | Command / Action |
|---|---|
| Missing indexes | .explain("executionStats") — look for COLLSCAN |
| Unused indexes | db.users.aggregate([{ $indexStats: {} }]) |
| Slow queries | db.setProfilingLevel(1, { slowms: 50 }) |
| Large documents | db.users.stats() — check avgObjSize |
| Memory pressure | db.serverStatus().mem or mongostat |
| Connection pool | maxPoolSize, monitor with mongostat |
| Working set vs RAM | Ensure index + hot data fits in memory |
| WriteConcern | Use w:1 for speed, w:"majority" for safety |
| Command | Purpose |
|---|---|
| mongostat | Real-time server stats |
| mongotop | Collection-level read/write time |
| mongodump / mongorestore | Backup / restore |
| mongoexport / mongoimport | JSON/CSV export/import |
| mongoreplay | Replay captured traffic |
| db.killOp() | Kill long-running operation |
| db.fsyncLock() | Lock for backup |
| db.fsyncUnlock() | Unlock after backup |