| name | mongodb |
| description | MongoDB patterns: schema design, aggregation pipeline, indexes, transactions, Atlas Search, Mongoose vs native driver |
MongoDB Skill
When to activate
- Designing a MongoDB schema (embedding vs referencing)
- Writing aggregation pipelines for complex queries
- Setting up indexes for query performance
- Using MongoDB transactions across multiple documents
- Integrating MongoDB Atlas Search (full-text)
- Choosing between Mongoose and the native driver
When NOT to use
- Relational data with many joins — use PostgreSQL
- When you need ACID transactions at scale — consider PostgreSQL
- Simple key-value caching — use Redis
- Analytics on large datasets — use a data warehouse
Instructions
When to embed vs reference
Embed when:
- Data is always accessed together (post + its comments if comments are few)
- Child data has no independent lifecycle
- Array size stays bounded (< 100 items, rarely grows to thousands)
Reference when:
- Data is accessed independently
- Array could grow unboundedly (all orders for a user)
- Many-to-many relationships
{
_id: ObjectId("..."),
email: "alice@example.com",
address: {
street: "123 Main St",
city: "San Francisco",
zip: "94105"
}
}
{ _id: ObjectId("user1"), email: "alice@example.com" }
{ _id: ObjectId("order1"), userId: ObjectId("user1"), total: 99.99 }
Native driver (Node.js)
import { MongoClient, ObjectId } from 'mongodb'
const client = new MongoClient(process.env.MONGODB_URI!)
const db = client.db('myapp')
const users = db.collection('users')
await client.connect()
const user = await users.findOne({ _id: new ObjectId(id) })
const result = await users.insertOne({ email, name, createdAt: new Date() })
await users.updateOne(
{ _id: new ObjectId(id) },
{ $set: { name }, $currentDate: { updatedAt: true } }
)
await users.deleteOne({ _id: new ObjectId(id) })
const recent = await users
.find({ active: true })
.({ : , : , : })
.({ : - })
.()
.()
Mongoose (schema-based ODM)
import mongoose, { Schema, model, type InferSchemaType } from 'mongoose'
const userSchema = new Schema({
email: { type: String, required: true, unique: true, lowercase: true },
name: { type: String, required: true },
role: { type: String, enum: ['user', 'admin'], default: 'user' },
tags: [String],
metadata: { type: Map, of: String },
createdAt: { type: Date, default: Date.now },
})
userSchema.index({ email: 1 }, { unique: true })
userSchema.index({ createdAt: -1 })
userSchema.index({ tags: })
userSchema.().(() {
..()[]
})
userSchema.(, () {
(.()) {
. = bcrypt.(., )
}
()
})
= < userSchema>
= model<>(, userSchema)
user = .(id).()
users = .({ : }).().()
user = .(
{ email },
{ : { name } },
{ : , : }
)
Aggregation pipeline — the most powerful MongoDB feature
db.orders.aggregate([
{ $match: {
status: 'completed',
createdAt: { $gte: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000) }
}},
{ $unwind: '$items' },
{ $group: {
_id: '$items.category',
totalRevenue: { $sum: { $multiply: ['$items.price', '$items.qty'] } },
orderCount: { $addToSet: '$_id' },
avgOrderValue: { $avg: '$total' },
}},
{ $project: {
category: '$_id',
totalRevenue: { $round: ['$totalRevenue', 2] },
orderCount: { $size: '$orderCount' },
avgOrderValue: { $round: ['$avgOrderValue', ] },
: ,
}},
{ : { : - } },
])
Common pipeline stages:
| Stage | Use for |
|---|
$match | Filter documents (put early to use indexes) |
$group | Aggregate: sum, count, avg, min, max, push |
$project | Reshape documents, add computed fields |
$unwind | Flatten array into multiple documents |
$lookup | Left join with another collection |
$sort | Sort results |
$limit / $skip | Pagination |
$facet | Multiple aggregations in one pass |
$bucket | Group into ranges (histogram) |
$addFields | Add/modify fields without changing the rest |
Lookup (join):
db.orders.aggregate([
{ $lookup: {
from: 'users',
localField: 'userId',
foreignField: '_id',
as: 'user',
pipeline: [
{ $project: { email: 1, name: 1 } }
]
}},
{ $unwind: '$user' },
])
Indexes
db.users.createIndex({ email: 1 }, { unique: true })
db.orders.createIndex({ userId: 1, createdAt: -1 })
db.posts.createIndex({ title: 'text', content: 'text' }, {
weights: { title: 10, content: 1 },
default_language: 'english'
})
db.posts.find({ $text: { $search: 'typescript patterns' } },
{ score: { $meta: 'textScore' } })
.sort({ score: { $meta: 'textScore' } })
db.orders.createIndex(
{ createdAt: 1 },
{ partialFilterExpression: { status: 'pending' } }
)
db.sessions.createIndex({ : }, { : })
Transactions
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 }
)
await ledger.insertOne(
{ from: fromId, to: toId, amount, createdAt: new Date() },
{ session }
)
})
} finally {
await session.endSession()
}
Update operators
{ $set: { name: 'Alice', 'address.city': 'NYC' } }
{ $inc: { views: 1, stock: -1 } }
{ $push: { tags: 'typescript' } }
{ $addToSet: { tags: 'typescript' } }
{ $pull: { tags: 'draft' } }
{ $pop: { history: -1 } }
{ $unset: { legacyField: '' } }
{ $currentDate: { updatedAt: true } }
Example
User: Build a MongoDB aggregation to find the top 5 content creators by total post views in the last 7 days, with their user info, broken down by post category.
Expected pipeline:
db.posts.aggregate([
{ $match: { publishedAt: { $gte: new Date(Date.now() - 7 * 86400000) } }},
{ $group: {
_id: { authorId: '$authorId', category: '$category' },
totalViews: { $sum: '$views' },
postCount: { $sum: 1 }
}},
{ $group: {
_id: '$_id.authorId',
totalViews: { $sum: '$totalViews' },
breakdown: { $push: { category: '$_id.category', views: '$totalViews' } }
}},
{ $sort: { totalViews: -1 } },
{ $limit: 5 },
{ $lookup: { from: 'users', localField: '_id', foreignField: '_id', as: 'user',
pipeline: [{ $project: { email: 1, name: 1 } }] }},
{ : },
])