MongoDB for Backend Developers
I spent my first year with MongoDB treating it like SQL with a different syntax. That was wrong. MongoDB shines when you embrace its document model properly.
Document Schema Design
Embedding vs Referencing
Embed when:
- Data is accessed together
- Child data is bounded (< 100 items)
- Updates are infrequent
// Embedded - User with addresses
{
_id: ObjectId("..."),
name: "Vikram Singh",
email: "[email protected]",
addresses: [
{ type: "home", city: "Mumbai", pincode: "400001" },
{ type: "office", city: "Mumbai", pincode: "400051" }
]
}
Reference when:
- Data grows unbounded
- Many-to-many relationships
- Data accessed independently
// Referenced - Order with products
// orders collection
{
_id: ObjectId("..."),
userId: ObjectId("user_id"),
products: [
{ productId: ObjectId("prod_1"), quantity: 2 },
{ productId: ObjectId("prod_2"), quantity: 1 }
],
total: 2500
}
// products collection (separate)
{
_id: ObjectId("prod_1"),
name: "Wireless Mouse",
price: 800
}
Aggregation Pipeline
The real power of MongoDB:
// Sales analytics example
db.orders.aggregate([
// Stage 1: Filter last 30 days
{
$match: {
createdAt: { $gte: new Date(Date.now() - 30*24*60*60*1000) }
}
},
// Stage 2: Group by product
{
$unwind: "$products"
},
// Stage 3: Calculate totals
{
$group: {
_id: "$products.productId",
totalQuantity: { $sum: "$products.quantity" },
totalRevenue: { $sum: { $multiply: ["$products.quantity", "$products.price"] } },
orderCount: { $sum: 1 }
}
},
// Stage 4: Sort by revenue
{
$sort: { totalRevenue: -1 }
},
// Stage 5: Get top 10
{
$limit: 10
}
]);
Indexing Strategies
Performance lives or dies by indexing:
// Single field index
db.users.createIndex({ email: 1 });
// Compound index (order matters!)
db.orders.createIndex({ userId: 1, createdAt: -1 });
// Text index for search
db.products.createIndex({ name: "text", description: "text" });
// Partial index (only index active users)
db.users.createIndex(
{ email: 1 },
{ partialFilterExpression: { isActive: true } }
);
Checking Index Usage
db.orders
.find({ userId: "123", status: "pending" })
.explain("executionStats");
Look for IXSCAN (good) vs COLLSCAN (bad, full collection scan).
Transactions
Yes, MongoDB supports ACID transactions:
const session = client.startSession();
try {
session.startTransaction();
await accounts.updateOne(
{ _id: fromAccountId },
{ $inc: { balance: -amount } },
{ session }
);
await accounts.updateOne(
{ _id: toAccountId },
{ $inc: { balance: amount } },
{ session }
);
await session.commitTransaction();
} catch (error) {
await session.abortTransaction();
throw error;
} finally {
session.endSession();
}
Performance Tips
- Use projection - Only fetch fields you need
- Limit results - Always paginate large collections
- Avoid $where - Write native queries instead
- Use bulk operations - For multiple writes
// Bulk write example
const ops = users.map(user => ({
updateOne: {
filter: { _id: user._id },
update: { $set: { lastLogin: new Date() } }
}
}));
await db.users.bulkWrite(ops);
MongoDB is fast when used correctly, slow when treated like SQL. Learn its strengths.