|$ curl https://forge-ai.dev/api/markdown?path=docs/databases/mongodb
$cat docs/mongodb.md
updated Recentlyยท50 min readยทpublished

MongoDB

โ—†MongoDBโ—†NoSQLโ—†Document Databaseโ—†Beginner to Advanced๐ŸŽฏFree Tools
Introduction

MongoDB is a document-oriented NoSQL database that stores data in flexible, JSON-like documents called BSON (Binary JSON). Unlike relational databases, MongoDB documents can have varying structures, making it ideal for rapidly evolving schemas and hierarchical data.

MongoDB's document model maps naturally to objects in application code, eliminating the object-relational impedance mismatch that plagues traditional ORM usage. Its aggregation framework provides powerful data transformation capabilities that rival SQL's analytical queries.

This guide covers CRUD operations, the aggregation pipeline, indexing strategies, multi-document transactions, and schema design patterns for production applications.

๐Ÿ“

note

MongoDB 7.0 introduced vector search, improved time-series collections, and enhanced change streams. This guide covers features available in MongoDB 6.0+.
Document Model

In MongoDB, data is organized into databases, collections (analogous to tables), and documents (analogous to rows). Documents are stored in BSON format, which supports all JSON types plus additional types like Date, ObjectId, Binary, and Decimal128.

document-model.js
JavaScript
1// A MongoDB document structure
2{
3 _id: ObjectId("665f1a2b3c4d5e6f7a8b9c0d"),
4 name: "Alice Johnson",
5 email: "alice@example.com",
6 age: 32,
7 isActive: true,
8 createdAt: ISODate("2025-01-15T10:30:00Z"),
9
10 // Embedded document (1:1 or 1:few)
11 address: {
12 street: "123 Main St",
13 city: "San Francisco",
14 state: "CA",
15 zip: "94102",
16 coordinates: { type: "Point", coordinates: [-122.4194, 37.7749] }
17 },
18
19 // Array of subdocuments (1:many, bounded)
20 preferences: [
21 { key: "theme", value: "dark" },
22 { key: "language", value: "en" },
23 { key: "notifications", value: "email" }
24 ],
25
26 // Array of references (1:many, unbounded)
27 orderIds: [
28 ObjectId("665f1b3c4d5e6f7a8b9c0e1f"),
29 ObjectId("665f1c4d5e6f7a8b9c0e2f3a")
30 ],
31
32 // Schema version for migrations
33 _schemaVersion: 2
34}
35
36// Collections are schema-flexible โ€” different documents
37// in the same collection can have different fields
38db.users.insertOne({
39 name: "Bob Smith",
40 email: "bob@example.com",
41 // No address, no preferences โ€” different shape, same collection
42});
โ„น

info

MongoDB's schema flexibility is powerful but dangerous without discipline. Always enforce a schema in your application layer using validation libraries like Zod or Mongoose schemas. Flexible schema does not mean no schema.
CRUD Operations

MongoDB provides a rich set of CRUD operations with dot-notation access for nested fields and array manipulation operators.

crud-operations.js
JavaScript
1// CREATE operations
2db.users.insertOne({
3 name: "Charlie Brown",
4 email: "charlie@example.com",
5 role: "developer",
6 skills: ["javascript", "python", "go"],
7 createdAt: new Date()
8});
9
10db.users.insertMany([
11 { name: "Diana Ross", email: "diana@example.com", role: "designer" },
12 { name: "Eve Wilson", email: "eve@example.com", role: "developer" },
13 { name: "Frank Lee", email: "frank@example.com", role: "manager" }
14]);
15
16// READ operations
17db.users.findOne({ email: "charlie@example.com" });
18
19db.users.find({
20 role: "developer",
21 skills: { $in: ["javascript", "typescript"] },
22 createdAt: { $gte: ISODate("2025-01-01") }
23}).sort({ createdAt: -1 }).limit(10).projection({
24 name: 1, email: 1, skills: 1
25});
26
27// UPDATE operations
28db.users.updateOne(
29 { _id: ObjectId("...") },
30 {
31 $set: { role: "senior developer", updatedAt: new Date() },
32 $push: { skills: { $each: ["rust", "postgresql"] } },
33 $inc: { version: 1 }
34 }
35);
36
37// Array updates
38db.users.updateOne(
39 { _id: ObjectId("...") },
40 { $pull: { skills: "python" } } // remove from array
41);
42
43db.users.updateOne(
44 { _id: ObjectId("...") },
45 { $addToSet: { skills: "rust" } } // add if not exists
46);
47
48// DELETE operations
49db.users.deleteOne({ _id: ObjectId("...") });
50db.users.deleteMany({ role: { $exists: false } });
Aggregation Pipeline

The aggregation pipeline is MongoDB's most powerful feature. It processes documents through a sequence of stages, each transforming the data. Think of it as SQL's SELECT pipeline broken into composable, readable stages.

aggregation-pipeline.js
JavaScript
1// E-commerce analytics pipeline
2db.orders.aggregate([
3 // Stage 1: Filter (like WHERE)
4 { $match: {
5 status: "completed",
6 createdAt: { $gte: ISODate("2025-01-01") }
7 }},
8
9 // Stage 2: Unwind array (like JOIN LATERAL)
10 { $unwind: "$items" },
11
12 // Stage 3: Lookup (like LEFT JOIN)
13 { $lookup: {
14 from: "products",
15 localField: "items.productId",
16 foreignField: "_id",
17 as: "product"
18 }},
19
20 // Stage 4: Deconstruct array to single field
21 { $unwind: "$product" },
22
23 // Stage 5: Compute new fields
24 { $addFields: {
25 itemRevenue: { $multiply: ["$items.quantity", "$items.unitPrice"] },
26 productCategory: "$product.category",
27 margin: {
28 $subtract: [
29 "$items.unitPrice",
30 { $multiply: ["$product.cost", 0.7] }
31 ]
32 }
33 }},
34
35 // Stage 6: Group (like GROUP BY)
36 { $group: {
37 _id: {
38 month: { $dateToString: { format: "%Y-%m", date: "$createdAt" } },
39 category: "$productCategory"
40 },
41 totalRevenue: { $sum: "$itemRevenue" },
42 totalOrders: { $sum: 1 },
43 avgOrderValue: { $avg: "$itemRevenue" },
44 totalMargin: { $sum: "$margin" },
45 uniqueCustomers: { $addToSet: "$userId" }
46 }},
47
48 // Stage 7: Reshape output
49 { $project: {
50 _id: 0,
51 month: "$_id.month",
52 category: "$_id.category",
53 totalRevenue: { $round: ["$totalRevenue", 2] },
54 totalOrders: 1,
55 avgOrderValue: { $round: ["$avgOrderValue", 2] },
56 totalMargin: { $round: ["$totalMargin", 2] },
57 customerCount: { $size: "$uniqueCustomers" }
58 }},
59
60 // Stage 8: Sort (like ORDER BY)
61 { $sort: { month: -1, totalRevenue: -1 } },
62
63 // Stage 9: Limit
64 { $limit: 50 }
65]);
retention-analysis.js
JavaScript
1// Advanced aggregation: User retention analysis
2db.orders.aggregate([
3 { $match: { createdAt: { $gte: ISODate("2025-01-01") } } },
4
5 // Get each user's first order date
6 { $group: {
7 _id: "$userId",
8 firstOrder: { $min: "$createdAt" },
9 orders: { $push: { date: "$createdAt", total: "$total" } }
10 }},
11
12 // Calculate cohort month
13 { $addFields: {
14 cohortMonth: { $dateToString: { format: "%Y-%m", date: "$firstOrder" } },
15 orderMonths: {
16 $map: {
17 input: "$orders",
18 as: "o",
19 in: { $dateToString: { format: "%Y-%m", date: "$$o.date" } }
20 }
21 }
22 }},
23
24 // Unwind months and count unique users per cohort-month
25 { $unwind: "$orderMonths" },
26 { $group: {
27 _id: { cohort: "$cohortMonth", activityMonth: "$orderMonths" },
28 activeUsers: { $sum: 1 }
29 }},
30
31 // Calculate retention rate
32 { $lookup: {
33 from: (function() { return null; })(), // self-reference in pipeline
34 let: { cohort: "$_id.cohort" },
35 pipeline: [
36 { $match: { $expr: { $eq: ["$cohortMonth", "$$cohort"] } } },
37 { $count: "total" }
38 ],
39 as: "cohortSize"
40 }},
41
42 { $project: {
43 cohort: "$_id.cohort",
44 month: "$_id.activityMonth",
45 activeUsers: 1,
46 retentionRate: {
47 $round: [
48 { $multiply: [
49 { $divide: ["$activeUsers", { $arrayElemAt: ["$cohortSize.total", 0] }] },
50 100
51 ]},
52 1
53 ]
54 }
55 }},
56
57 { $sort: { cohort: 1, month: 1 } }
58]);
๐Ÿ”ฅ

pro tip

The aggregation pipeline is evaluated lazily โ€” stages only process documents when the final result is requested. Place $match stages as early as possible to reduce the number of documents flowing through subsequent stages.
Indexing Strategies

MongoDB supports several index types to optimize different query patterns. Choosing the right index is critical for performance.

mongodb-indexes.js
JavaScript
1// Single field index
2db.users.createIndex({ email: 1 }, { unique: true });
3
4// Compound index (order matters!)
5db.orders.createIndex({ userId: 1, createdAt: -1 });
6db.orders.createIndex({ status: 1, createdAt: -1, total: -1 });
7
8// Multikey index (automatically indexes array elements)
9db.users.createIndex({ skills: 1 });
10
11// Text index (full-text search)
12db.articles.createIndex({ title: "text", body: "text" }, {
13 weights: { title: 10, body: 1 },
14 default_language: "english"
15});
16
17// Geospatial index
18db.locations.createIndex({ "address.coordinates": "2dsphere" });
19
20// TTL index (auto-delete documents after time)
21db.sessions.createIndex({ expiresAt: 1 }, { expireAfterSeconds: 0 });
22
23// Partial index (index only a subset)
24db.orders.createIndex(
25 { userId: 1, createdAt: -1 },
26 { partialFilterExpression: { status: "completed" } }
27);
28
29// Sparse index (skip documents without the field)
30db.users.createIndex({ phone: 1 }, { sparse: true });
31
32// Hashed index (for hash-based sharding)
33db.users.createIndex({ email: "hashed" });
34
35// Wildcard index (index all fields matching a pattern)
36db.products.createIndex({ "metadata.$**": 1 });
37
38// Explain query execution
39db.users.find({ email: "test@example.com" }).explain("executionStats");
40
41// Check index usage statistics
42db.users.aggregate([{ $indexStats: {} }]);
โš 

warning

MongoDB indexes follow the ESR rule (Equality, Sort, Range) for compound indexes. Place equality fields first, then sort fields, then range fields. A compound index on {status: 1, createdAt: -1} efficiently supports queries that filter by status and sort by createdAt.
Multi-Document Transactions

MongoDB supports multi-document ACID transactions since version 4.0. While single-document operations are always atomic, transactions are needed when you must atomically update multiple documents across collections.

transactions.js
JavaScript
1const { MongoClient } = require("mongodb");
2const client = new MongoClient(process.env.MONGODB_URI);
3
4async function transferFunds(fromId, toId, amount) {
5 const session = client.startSession();
6
7 try {
8 const result = await session.withTransaction(async () => {
9 const users = client.db("myapp").collection("users");
10
11 // Debit sender
12 const debitResult = await users.updateOne(
13 { _id: fromId, balance: { $gte: amount } },
14 { $inc: { balance: -amount } },
15 { session }
16 );
17
18 if (debitResult.modifiedCount === 0) {
19 throw new Error("Insufficient funds");
20 }
21
22 // Credit receiver
23 await users.updateOne(
24 { _id: toId },
25 { $inc: { balance: amount } },
26 { session }
27 );
28
29 // Record transaction
30 await client.db("myapp").collection("transactions").insertOne({
31 fromId, toId, amount,
32 createdAt: new Date(),
33 type: "transfer"
34 }, { session });
35
36 return { success: true };
37 });
38
39 return result;
40 } finally {
41 await session.endSession();
42 }
43}
44
45// Usage
46await transferFunds(
47 ObjectId("from-user-id"),
48 ObjectId("to-user-id"),
49 100.00
50);
โš 

warning

Transactions have performance overhead. MongoDB uses WiredTiger snapshot isolation for transactions, which can causeTransactionConflict errors under high contention. Prefer single-document atomic operations ($inc, $push, $pull) whenever possible.
Schema Design Patterns

MongoDB schema design is about making deliberate trade-offs between embedding (denormalization) and referencing (normalization). These patterns guide those decisions.

schema-patterns.js
JavaScript
1// Pattern 1: Extended Reference Pattern
2// Store frequently accessed fields alongside the reference
3db.orders.aggregate([
4 { $lookup: {
5 from: "users",
6 localField: "userId",
7 foreignField: "_id",
8 as: "user",
9 pipeline: [
10 { $project: { name: 1, email: 1 } } // only needed fields
11 ]
12 }},
13 { $unwind: "$user" }
14]);
15
16// Pattern 2: Subset Pattern
17// Embed only recent/frequent data, reference older data
18db.users.updateOne(
19 { _id: userId },
20 {
21 $push: {
22 recentOrders: {
23 $each: [newOrder],
24 $slice: -10 // keep only last 10
25 }
26 }
27 }
28);
29
30// Pattern 3: Bucket Pattern (time-series)
31// Group related time-series data into time-based buckets
32db.sensorData.updateOne(
33 { sensorId: "s1", date: ISODate("2025-01-15"), count: { $lt: 500 } },
34 {
35 $push: { measurements: { ts: new Date(), value: 42.5 } },
36 $inc: { count: 1 },
37 $setOnInsert: { createdAt: new Date() }
38 },
39 { upsert: true }
40);
41
42// Pattern 4: Computed Pattern
43// Pre-compute expensive aggregations
44db.products.updateOne(
45 { _id: productId },
46 {
47 $set: {
48 "stats.avgRating": { $avg: "$reviews.rating" },
49 "stats.reviewCount": { $size: "$reviews" },
50 "stats.lastReviewAt": { $max: "$reviews.createdAt" }
51 }
52 }
53);
54
55// Pattern 5: Document Versioning Pattern
56db.articles.updateOne(
57 { _id: articleId },
58 {
59 $set: {
60 title: "Updated Title",
61 content: "Updated content",
62 _schemaVersion: 3,
63 updatedAt: new Date()
64 },
65 $push: {
66 versions: {
67 $each: [{ title: "Old Title", content: "Old content", savedAt: new Date() }],
68 $slice: -10
69 }
70 }
71 }
72);
โ„น

info

The golden rule of MongoDB schema design: data that is accessed together should be stored together. If you frequently query orders with user data, embed the user info in the order document. If you rarely need it, reference the user ID.
Change Streams

Change streams provide a real-time feed of data changes, similar to PostgreSQL's logical replication. They enable event-driven architectures, real-time updates, and data synchronization between systems.

change-streams.js
JavaScript
1// Watch for changes on a collection
2const changeStream = db.orders.watch([
3 { $match: { "operationType": { $in: ["insert", "update"] } } }
4]);
5
6changeStream.on("change", (change) => {
7 switch (change.operationType) {
8 case "insert":
9 console.log("New order:", change.fullDocument);
10 // Trigger email notification, inventory update, etc.
11 break;
12 case "update":
13 console.log("Order updated:", change.documentKey._id);
14 console.log("Updated fields:", change.updateDescription.updatedFields);
15 break;
16 }
17});
18
19// Watch specific fields only
20const pipeline = [
21 { $match: { "fullDocument.status": "completed" } },
22 { $project: { fullDocument: 1, operationType: 1 } }
23];
24const stream = db.orders.watch(pipeline, { fullDocument: "updateLookup" });
25
26// Resume from a specific point
27const resumeToken = ...; // save this for later
28const resumedStream = db.orders.watch([], { resumeAfter: resumeToken });
29
30// Change streams with the Node.js driver
31const collection = client.db("myapp").collection("orders");
32const pipeline = [{ $match: { "operationType": "insert" } }];
33const changeStream = collection.watch(pipeline, {
34 fullDocument: "updateLookup"
35});
36
37for await (const event of changeStream) {
38 console.log("Change event:", event);
39 // Process event...
40}
Performance Monitoring

MongoDB provides built-in profiling, server statistics, and explain plans to help you identify and fix performance issues.

profiling.js
JavaScript
1// Enable profiling (log slow queries)
2db.setProfilingLevel(1, { slowms: 100 }); // log queries > 100ms
3
4// Check slow queries
5db.system.profile.find({ millis: { $gt: 100 } })
6 .sort({ ts: -1 })
7 .limit(10)
8 .pretty();
9
10// Server status
11db.serverStatus(); // comprehensive server stats
12db.serverStatus().connections; // connection info
13db.serverStatus().wiredTiger; // storage engine stats
14
15// Collection stats
16db.orders.stats();
17db.orders.stats({ indexDetails: true });
18
19// Current operations
20db.currentOp({ "secs_running": { $gte: 5 } });
21
22// Explain a query
23db.orders.find({ userId: "u1", status: "completed" })
24 .explain("executionStats");
25
26// Key metrics to watch:
27// - executionStats.totalDocsExamined (should be close to nReturned)
28// - executionStats.totalKeysExamined (should be close to nReturned)
29// - executionStats.executionTimeMillis
30// - If totalDocsExamined >> nReturned, you need a better index
31
32// Index suggestion
33db.orders.find({ userId: "u1", status: "completed", createdAt: { $gte: new Date() } })
34 .explain("executionStats");
35// If it does a collection scan, create:
36// db.orders.createIndex({ userId: 1, status: 1, createdAt: -1 });
โœ“

best practice

In production, set profiling level to 2 (log all operations) for short diagnostic windows, then revert to level 1. Use MongoDB Atlas or Percona Monitoring for continuous performance visibility.