| name | mongodb-patterns |
| description | Document modeling, aggregation pipeline, indexing strategy, change streams, and multi-document transactions. |
MongoDB Patterns
Document database design and query optimization for MongoDB.
Document Modeling Strategies
interface Order {
_id: ObjectId
customerId: ObjectId
status: 'pending' | 'paid' | 'shipped'
items: OrderItem[]
shippingAddress: Address
createdAt: Date
}
interface OrderItem {
productId: ObjectId
name: string
price: number
quantity: number
}
interface Product {
_id: ObjectId
name: string
price: number
categoryId: ObjectId
reviews: never
}
interface SensorBucket {
_id: ObjectId
sensorId: string
startTime: Date
endTime: Date
count: number
measurements: {
timestamp: Date
value: number
}[]
}
Indexing Strategy
db.orders.createIndex({
status: 1,
createdAt: -1,
total: 1
})
db.orders.createIndex(
{ customerId: 1, createdAt: -1 },
{ partialFilterExpression: { status: 'pending' } }
)
db.products.createIndex({ name: 'text', description: 'text' })
db.sessions.createIndex(
{ createdAt: 1 },
{ expireAfterSeconds: 86400 }
)
db.events.createIndex({ 'metadata.$**': 1 })
Aggregation Pipeline
const pipeline = [
{ $match: {
createdAt: { $gte: new Date('2025-01-01'), $lt: new Date('2025-02-01') },
status: 'paid'
}},
{ $unwind: '$items' },
{ $group: {
_id: '$items.productId',
productName: { $first: '$items.name' },
totalRevenue: { $sum: { $multiply: ['$items.price', '$items.quantity'] } },
totalSold: { $sum: '$items.quantity' },
orderCount: { $addToSet: '$_id' }
}},
{ $addFields: {
orderCount: { $size: '$orderCount' },
avgOrderValue: { $divide: ['$totalRevenue', { $size: '$orderCount' }] }
}},
{ $sort: { totalRevenue: -1 } },
{ : },
{ : {
: ,
: ,
: ,
: [{ : { : } }],
:
}}
]
results = db..(pipeline).()
Change Streams (Real-time Reactivity)
async function watchOrderChanges(): Promise<void> {
const pipeline = [
{ $match: {
operationType: { $in: ['insert', 'update'] },
'fullDocument.status': 'paid'
}}
]
const changeStream = db.orders.watch(pipeline, {
fullDocument: 'updateLookup',
resumeAfter: await getLastResumeToken()
})
changeStream.on('change', async (event) => {
try {
await processOrderPayment(event.fullDocument!)
await saveResumeToken(event._id)
} catch (err) {
console.error('Change stream processing failed:', err)
}
})
changeStream.on('error', (err) => {
console.error('Change stream error:', err)
( (), )
})
}
Multi-Document Transactions
async function transferFunds(
fromAccountId: string,
toAccountId: string,
amount: number
): Promise<void> {
const session = client.startSession()
try {
await session.withTransaction(async () => {
const from = await db.accounts.findOne(
{ _id: new ObjectId(fromAccountId) },
{ session }
)
if (!from || from.balance < amount) {
throw new Error('Insufficient funds')
}
await db.accounts.updateOne(
{ _id: new ObjectId(fromAccountId) },
{ $inc: { balance: -amount } },
{ session }
)
await db.accounts.updateOne(
{ _id: new ObjectId(toAccountId) },
{ $inc: { balance: amount } },
{ session }
)
db..({
: fromAccountId,
: toAccountId,
amount,
: ()
}, { session })
})
} {
session.()
}
}
Checklist
Anti-Patterns
- Unbounded arrays: reviews/comments embedded in parent (grows forever, hits 16MB)
- Missing indexes: full collection scans on frequently queried fields
- $lookup in hot paths: use denormalization, not joins, for read-heavy queries
- Storing related data in separate collections when always read together
- Using MongoDB as a relational database (normalize everything)
- Not using write concern
majority for critical writes (data loss risk)