I switched between relational and document databases enough times over the years that I finally got tired of googling the same MongoDB syntax over and over. So I built this cheat sheet the way I actually use MongoDB day to day — from spinning up a local instance, to writing aggregation pipelines, to locking down a production cluster with proper authentication.
If you’re building a Node.js API, prototyping fast, or just trying to get comfortable with document-based data modeling, this is meant to be the single page you keep open. I’ve tried to cover the commands you’ll type constantly and the ones you’ll only need once a year but always forget.
Let’s dive in.
Table of Contents
- Getting Started: Connecting to MongoDB
- Database and Collection Management
- Data Types in MongoDB
- CRUD Operations: Insert, Find, Update, Delete
- Query Operators Reference
- Sorting, Limiting, and Pagination
- The Aggregation Framework
- Indexes and Performance
- Schema Design and Data Modeling
- Transactions
- User Management and Security
- Backup and Restore
- Replication and Sharding Basics
- Troubleshooting Common Errors
- Best Practices
- Real-World Use Cases
- Frequently Asked Questions
- Common Mistakes to Avoid
- Interview Questions
- Printable Quick-Reference Summary
- Official Documentation Links
1. Getting Started: Connecting to MongoDB
I mostly use the mongosh shell (the modern replacement for the legacy mongo shell) for quick checks, and a driver in application code for everything else.
# Connect to a local instance
mongosh
# Connect to a specific host and port
mongosh --host 127.0.0.1 --port 27017
# Connect with authentication
mongosh "mongodb://username:password@localhost:27017/mydb"
# Connect to MongoDB Atlas (cloud)
mongosh "mongodb+srv://cluster0.mongodb.net/mydb" --username myuser
Once inside the shell:
// Show current database
db
// List all databases
show dbs
// Switch to (or create) a database
use myapp
// Show current server status
db.serverStatus()
// Show MongoDB version
db.version()
One thing that catches newcomers off guard: use myapp doesn’t actually create the database until you insert data into a collection. MongoDB creates databases and collections lazily, on first write.
2. Database and Collection Management
| Task | Command |
|---|---|
| List databases | show dbs |
| Switch/create database | use dbname |
| Show current database | db |
| Drop current database | db.dropDatabase() |
| List collections | show collections |
| Create a collection explicitly | db.createCollection("users") |
| Drop a collection | db.users.drop() |
| Rename a collection | db.users.renameCollection("app_users") |
| Get collection stats | db.users.stats() |
// Create a collection with schema validation
db.createCollection("users", {
validator: {
$jsonSchema: {
bsonType: "object",
required: ["username", "email"],
properties: {
username: { bsonType: "string" },
email: { bsonType: "string", pattern: "^.+@.+$" },
age: { bsonType: "int", minimum: 0 }
}
}
}
})
I use schema validation on any collection where I want MongoDB’s flexibility but still need some guardrails — it’s saved me from bad data creeping in during early development when I was still iterating on the app’s models.
3. Data Types in MongoDB
MongoDB stores data as BSON (Binary JSON), which supports more types than plain JSON.
| Type | Example | Notes |
|---|---|---|
| String | "hello" | UTF-8 |
| Number (Int32) | NumberInt(42) | 32-bit integer |
| Number (Int64) | NumberLong(42) | 64-bit integer |
| Double | 42.5 | Default number type |
| Decimal128 | NumberDecimal("42.50") | For precise decimal math (currency) |
| Boolean | true / false | |
| Date | ISODate("2024-01-01") | Stored as UTC |
| Array | ["a", "b", "c"] | Ordered list |
| Object | { street: "Main St" } | Embedded document |
| ObjectId | ObjectId("64f...") | Default _id type, 12-byte unique ID |
| Null | null | |
| Binary Data | BinData(...) | For files, hashes, etc. |
// Every document gets a unique _id automatically if not provided
db.users.insertOne({ username: "johndoe" })
// _id: ObjectId("...") is generated automatically
I default to Decimal128 for any money field, the same way I’d use DECIMAL in a relational database — floating point rounding errors are just as real here.
4. CRUD Operations: Insert, Find, Update, Delete
Insert
// Insert a single document
db.users.insertOne({
username: "johndoe",
email: "john@example.com",
age: 29,
createdAt: new Date()
})
// Insert multiple documents
db.users.insertMany([
{ username: "alice", email: "alice@example.com" },
{ username: "bob", email: "bob@example.com" }
])
Expected output:
{
acknowledged: true,
insertedId: ObjectId("64f1a2b3c4d5e6f7a8b9c0d1")
}
Find
// Find all documents
db.users.find()
// Find with a filter
db.users.find({ username: "johndoe" })
// Find one document
db.users.findOne({ email: "john@example.com" })
// Find with projection (return only specific fields)
db.users.find({}, { username: 1, email: 1, _id: 0 })
// Pretty-print results in the shell
db.users.find().pretty()
Update
// Update a single document
db.users.updateOne(
{ username: "johndoe" },
{ $set: { email: "newemail@example.com" } }
)
// Update multiple documents
db.users.updateMany(
{ status: "inactive" },
{ $set: { status: "archived" } }
)
// Upsert (insert if not found)
db.users.updateOne(
{ username: "newuser" },
{ $set: { email: "new@example.com" } },
{ upsert: true }
)
// Replace an entire document
db.users.replaceOne(
{ username: "johndoe" },
{ username: "johndoe", email: "john@newdomain.com" }
)
Delete
// Delete a single document
db.users.deleteOne({ username: "johndoe" })
// Delete multiple documents
db.users.deleteMany({ status: "archived" })
// Delete all documents in a collection (keeps the collection itself)
db.users.deleteMany({})
Same rule as any database: I always run the equivalent find() with the same filter before running deleteMany() or updateMany(), just to see exactly what I’m about to touch.
5. Query Operators Reference
| Operator | Meaning | Example |
|---|---|---|
$eq | Equals | { age: { $eq: 30 } } |
$ne | Not equals | { age: { $ne: 30 } } |
$gt / $gte | Greater than / or equal | { age: { $gt: 18 } } |
$lt / $lte | Less than / or equal | { age: { $lt: 65 } } |
$in | Matches any value in array | { status: { $in: ["active", "pending"] } } |
$nin | Matches none of the values | { status: { $nin: ["banned"] } } |
$and | Logical AND | { $and: [{ age: { $gt: 18 } }, { status: "active" }] } |
$or | Logical OR | { $or: [{ status: "active" }, { vip: true }] } |
$not | Negates a condition | { age: { $not: { $lt: 18 } } } |
$exists | Field presence check | { phone: { $exists: true } } |
$regex | Pattern matching | { email: { $regex: "@gmail.com$" } } |
$elemMatch | Array element matches condition | { scores: { $elemMatch: { $gt: 80 } } } |
$size | Array length | { tags: { $size: 3 } } |
$all | Array contains all values | { tags: { $all: ["mongo", "db"] } } |
// Combining operators
db.orders.find({
status: { $in: ["shipped", "delivered"] },
total: { $gte: 50, $lte: 500 }
})
// Nested field query
db.users.find({ "address.city": "New York" })
6. Sorting, Limiting, and Pagination
// Sort ascending (1) or descending (-1)
db.users.find().sort({ createdAt: -1 })
// Limit results
db.users.find().limit(10)
// Skip for pagination
db.users.find().skip(20).limit(10)
// Count matching documents
db.users.countDocuments({ status: "active" })
// Combine sort, skip, and limit for a paginated query
db.orders.find({ status: "shipped" })
.sort({ createdAt: -1 })
.skip(0)
.limit(25)
For large collections, I avoid skip() on deep pagination since it gets slower the further you page — I switch to cursor-based pagination using _id or a timestamp instead once a collection gets large.
7. The Aggregation Framework
This is where MongoDB really earns its keep for analytics-style queries. Think of it as a pipeline — each stage transforms the data and passes it to the next.
// Basic aggregation: total spent per user
db.orders.aggregate([
{ $match: { status: "completed" } },
{ $group: { _id: "$userId", totalSpent: { $sum: "$total" } } },
{ $sort: { totalSpent: -1 } },
{ $limit: 10 }
])
// $project to reshape output
db.users.aggregate([
{ $project: { username: 1, email: 1, fullName: { $concat: ["$firstName", " ", "$lastName"] } } }
])
// $lookup — the MongoDB equivalent of a SQL join
db.orders.aggregate([
{
$lookup: {
from: "users",
localField: "userId",
foreignField: "_id",
as: "userDetails"
}
},
{ $unwind: "$userDetails" }
])
// $unwind — flatten an array field into separate documents
db.orders.aggregate([
{ $unwind: "$items" },
{ $group: { _id: "$items.productId", totalSold: { $sum: "$items.quantity" } } }
])
// $facet — run multiple aggregation pipelines in parallel
db.products.aggregate([
{
$facet: {
byCategory: [{ $group: { _id: "$category", count: { $sum: 1 } } }],
priceStats: [{ $group: { _id: null, avgPrice: { $avg: "$price" } } }]
}
}
])
I use $lookup constantly to replicate join-style reporting, and $facet whenever I want to build a dashboard summary in a single round trip instead of firing off five separate queries.
8. Indexes and Performance
// Create a single-field index
db.users.createIndex({ email: 1 })
// Create a unique index
db.users.createIndex({ email: 1 }, { unique: true })
// Compound index
db.orders.createIndex({ status: 1, createdAt: -1 })
// Text index for search
db.articles.createIndex({ content: "text" })
// TTL index — automatically expire documents (great for sessions/logs)
db.sessions.createIndex({ createdAt: 1 }, { expireAfterSeconds: 3600 })
// List indexes on a collection
db.users.getIndexes()
// Drop an index
db.users.dropIndex("email_1")
// Analyze query performance
db.users.find({ email: "john@example.com" }).explain("executionStats")
Same rule of thumb as any database: index the fields you filter, sort, and join on. TTL indexes in particular are one of my favorite MongoDB features — I’ve used them for session storage and rate-limit tracking without needing a separate cron job to clean up expired data.
9. Schema Design and Data Modeling
MongoDB gives you two main modeling strategies, and picking the right one per relationship matters a lot.
Embedding (nesting related data inside a document):
{
_id: ObjectId("..."),
username: "johndoe",
address: {
street: "123 Main St",
city: "New York",
zip: "10001"
}
}
Good for data that’s always accessed together and doesn’t grow unbounded — like a user’s address or profile settings.
Referencing (storing an ID and looking it up separately):
// users collection
{ _id: ObjectId("user1"), username: "johndoe" }
// orders collection
{ _id: ObjectId("order1"), userId: ObjectId("user1"), total: 99.99 }
Better for data that grows large or is shared across many documents — like a user’s order history, which could have thousands of entries.
My general rule: embed when the relationship is one-to-few and the nested data is always read together with the parent. Reference when the relationship is one-to-many at scale, or many-to-many.
10. Transactions
MongoDB has supported multi-document ACID transactions since version 4.0, which changed how I think about it for financial or multi-step operations.
const session = db.getMongo().startSession()
session.startTransaction()
try {
const users = session.getDatabase("myapp").users
const accounts = session.getDatabase("myapp").accounts
users.updateOne({ _id: userId }, { $inc: { balance: -100 } })
accounts.updateOne({ _id: accountId }, { $inc: { balance: 100 } })
session.commitTransaction()
} catch (error) {
session.abortTransaction()
throw error
} finally {
session.endSession()
}
For a single document, updates are always atomic by default in MongoDB, so you often don’t need a full transaction. I reach for explicit transactions only when I’m touching multiple documents or multiple collections that need to succeed or fail together.
11. User Management and Security
// Create an admin user (run from the admin database)
use admin
db.createUser({
user: "adminUser",
pwd: "StrongPassword123!",
roles: [{ role: "userAdminAnyDatabase", db: "admin" }]
})
// Create an application-specific user with limited privileges
use myapp
db.createUser({
user: "appUser",
pwd: "AnotherStrongPassword456!",
roles: [{ role: "readWrite", db: "myapp" }]
})
// List users
db.getUsers()
// Update a user's roles
db.updateUser("appUser", { roles: [{ role: "read", db: "myapp" }] })
// Drop a user
db.dropUser("appUser")
// Show current user's roles
db.runCommand({ connectionStatus: 1 })
Security tips I follow on every project:
- Enable authentication (
--authorsecurity.authorization: enabledin the config file) — MongoDB doesn’t require auth by default on a fresh local install, which is a common cause of exposed databases. - Use role-based access control and give each application user only the roles it needs (
read,readWrite, notdbOwnerunless truly necessary). - Never expose MongoDB directly to the public internet without a firewall, VPN, or IP allowlist.
- Enable TLS/SSL for connections in production.
- Sanitize and validate all user input in application code — MongoDB queries built from unsanitized input can still be vulnerable to injection-style attacks (e.g., passing an object where a string is expected).
- Rotate credentials and audit
db.getUsers()periodically.
12. Backup and Restore
# Backup an entire database
mongodump --db myapp --out /backup/2024-01-01
# Backup a specific collection
mongodump --db myapp --collection users --out /backup/2024-01-01
# Restore a database
mongorestore --db myapp /backup/2024-01-01/myapp
# Backup with authentication
mongodump --uri="mongodb://user:pass@localhost:27017/myapp" --out /backup
# Export a collection to JSON
mongoexport --db myapp --collection users --out users.json
# Import a collection from JSON
mongoimport --db myapp --collection users --file users.json
For production systems, I pair mongodump snapshots with MongoDB Atlas’s continuous backup feature (or replica set oplog-based backups if self-hosted) so I can do point-in-time recovery rather than relying only on daily dumps.
13. Replication and Sharding Basics
// Initiate a replica set
rs.initiate({
_id: "rs0",
members: [
{ _id: 0, host: "localhost:27017" },
{ _id: 1, host: "localhost:27018" },
{ _id: 2, host: "localhost:27019" }
]
})
// Check replica set status
rs.status()
// Check which node is primary
rs.isMaster()
// Enable sharding on a database
sh.enableSharding("myapp")
// Shard a collection on a chosen key
sh.shardCollection("myapp.orders", { userId: 1 })
// Check sharding status
sh.status()
I’ve only needed sharding on a handful of projects that hit real scale, but replica sets I consider close to mandatory for any production deployment — they’re what give you automatic failover if the primary node goes down.
14. Troubleshooting Common Errors
| Error | Likely Cause | Fix |
|---|---|---|
MongoServerError: Authentication failed | Wrong username/password or wrong auth database | Confirm credentials and the authSource parameter in the connection string |
MongoNetworkError: connect ECONNREFUSED | MongoDB service isn’t running or wrong host/port | Check mongod is running, verify host and port |
E11000 duplicate key error | Violates a unique index | Check existing documents or use upsert logic intentionally |
BSONObjectTooLarge | Document exceeds 16MB limit | Split large data across multiple documents or use GridFS for files |
not primary and slaveOk=false | Writing to a secondary node in a replica set | Ensure writes go to the primary, or check connection settings |
Query exceeded time limit | Missing index or overly broad query on a large collection | Run .explain() and add an appropriate index |
WiredTiger error on startup | Corrupted data files, often from an unclean shutdown | Restore from backup or run repair (mongod --repair) as a last resort |
// Check current operations (useful for stuck queries)
db.currentOp()
// Kill a specific operation
db.killOp(12345)
// Check server logs for recent issues
db.adminCommand({ getLog: "global" })
15. Best Practices
- Design your schema around your query patterns, not around normalization habits carried over from relational databases.
- Keep documents under a few hundred KB when possible — even though the hard limit is 16MB, huge documents hurt read/write performance.
- Use indexes deliberately; check
.explain()before assuming a query needs one. - Prefer
updateOne/updateManywith$setover full document replacement to avoid accidentally wiping fields. - Use schema validation on collections where data consistency matters, even though MongoDB doesn’t require a fixed schema.
- Always specify a projection in production
find()queries instead of returning entire documents by default. - Use connection pooling in your driver instead of opening a new connection per request.
- Enable authentication and access control from day one, even in development, so it’s not an afterthought before launch.
16. Real-World Use Cases
Product catalogs: MongoDB’s flexible schema has been genuinely useful for e-commerce catalogs where different product categories have wildly different attributes — a laptop has different specs than a t-shirt, and forcing that into a rigid relational schema always felt clunky.
Real-time analytics dashboards: I’ve built dashboards using the aggregation framework’s $facet and $group stages to compute multiple summary statistics in a single query instead of hitting the database repeatedly.
Session and cache storage: TTL indexes make MongoDB a solid fit for storing user sessions or short-lived tokens that need to expire automatically.
Content management systems: Embedding comments, tags, and metadata directly inside article documents has made read performance excellent for content-heavy applications where writes are far less frequent than reads.
17. Frequently Asked Questions
Is MongoDB schema-less? Not entirely — it’s schema-flexible. You can enforce structure with $jsonSchema validation, but by default, documents in the same collection can have different fields.
What’s the difference between updateOne and findOneAndUpdate? updateOne returns a write result (matched/modified counts). findOneAndUpdate returns the actual document (before or after the update, depending on options), which is useful when you need the updated data immediately.
How do I find duplicate values in a field?
db.users.aggregate([
{ $group: { _id: "$email", count: { $sum: 1 } } },
{ $match: { count: { $gt: 1 } } }
])
How do I copy a collection?
db.users.aggregate([{ $match: {} }, { $out: "users_copy" }])
What happens if I query a field that doesn’t exist on some documents? MongoDB simply treats it as null/non-existent for that document — it won’t error out, which is both a strength and a source of subtle bugs if you’re not careful with $exists checks.
Is MongoDB ACID-compliant? Single-document operations are always atomic. Multi-document ACID transactions are supported since version 4.0 for replica sets, and since 4.2 for sharded clusters.
How is _id different from a normal field? _id is required, automatically indexed, and unique per collection. If you don’t provide one on insert, MongoDB generates an ObjectId for you.
18. Common Mistakes to Avoid
- Running
deleteMany({})orupdateMany({}, ...)without double-checking the filter first. - Treating MongoDB like a relational database and over-normalizing data into too many collections with excessive
$lookupjoins. - Ignoring the 16MB document size limit until it becomes a production incident.
- Not creating indexes for frequently queried fields, then wondering why queries are slow at scale.
- Using
skip()for deep pagination on large collections instead of cursor-based pagination. - Storing large binary files directly in documents instead of using GridFS or external object storage.
- Leaving authentication disabled on a development instance that later gets exposed accidentally.
- Forgetting that array updates with
$pushcan grow a document unbounded over time if there’s no cap or archiving strategy.
19. Interview Questions
- What’s the difference between embedding and referencing in MongoDB schema design?
- Explain how the aggregation pipeline works and name three common stages.
- What is an
ObjectId, and what information is encoded in it? - How does MongoDB handle transactions, and when did multi-document ACID transactions become available?
- What’s the difference between
$lookupand a SQLJOIN? - How do TTL indexes work, and where would you use one?
- What’s the maximum document size in MongoDB, and how would you handle larger data?
- Explain the difference between a replica set and a sharded cluster.
- How would you design a schema for a one-to-many relationship with millions of “many” records?
- What’s the difference between
find()andaggregate()? - How does MongoDB achieve high availability?
- What are the trade-offs of using an unstructured/dynamic schema?
20. Printable Quick-Reference Summary
DATABASES show dbs; use dbname; db.dropDatabase();
COLLECTIONS show collections; db.createCollection(); db.coll.drop();
INSERT db.coll.insertOne({...}); db.coll.insertMany([...]);
FIND db.coll.find({filter}); db.coll.findOne({filter});
UPDATE db.coll.updateOne(filter, { $set: {...} });
DELETE db.coll.deleteOne(filter); db.coll.deleteMany(filter);
OPERATORS $eq $ne $gt $gte $lt $lte $in $nin $and $or $exists $regex
SORT/LIMIT .sort({field:1/-1}).skip(n).limit(n)
AGGREGATE db.coll.aggregate([{$match},{$group},{$sort},{$lookup}])
INDEXES db.coll.createIndex({field:1}); db.coll.getIndexes();
TRANSACTIONS session.startTransaction(); session.commitTransaction();
USERS db.createUser({...}); db.getUsers(); db.dropUser();
BACKUP mongodump --db name --out /path
RESTORE mongorestore --db name /path/name
Print this section out or pin it to a second monitor — it covers the commands that make up the bulk of daily MongoDB work.
21. Official Documentation Links
- MongoDB Manual: https://www.mongodb.com/docs/manual/
- MongoDB Aggregation Pipeline Reference: https://www.mongodb.com/docs/manual/core/aggregation-pipeline/
- MongoDB Query Operators: https://www.mongodb.com/docs/manual/reference/operator/query/
- mongodump / mongorestore Docs: https://www.mongodb.com/docs/database-tools/mongodump/
- MongoDB Security Checklist: https://www.mongodb.com/docs/manual/administration/security-checklist/
I revisit this sheet every time MongoDB ships a new major version, because the aggregation framework in particular keeps getting new operators worth knowing. Bookmark it, and it’ll stay useful well past today.