Mongoose Aggregation Framework is MongoDB's powerful data processing tool that allows complex data transformation and calculation operations on documents. Mongoose provides an interface fully compatible with MongoDB aggregation pipelines.
Basic Aggregation Operations
Using aggregate() Method
javascriptconst results = await User.aggregate([ { $match: { age: { $gte: 18 } } }, { $group: { _id: '$city', count: { $sum: 1 } } } ]);
Aggregation Pipeline Stages
1. $match - Filter Documents
javascript// Filter users aged 18 or older const results = await User.aggregate([ { $match: { age: { $gte: 18 } } } ]); // Multi-condition filtering const results = await User.aggregate([ { $match: { age: { $gte: 18 }, status: 'active' }} ]);
2. $group - Group Statistics
javascript// Group by city and count users const results = await User.aggregate([ { $group: { _id: '$city', count: { $sum: 1 }, avgAge: { $avg: '$age' } }} ]); // Multi-field grouping const results = await Order.aggregate([ { $group: { _id: { city: '$city', status: '$status' }, totalAmount: { $sum: '$amount' }, count: { $sum: 1 } }} ]);
3. $project - Project Fields
javascript// Select specific fields const results = await User.aggregate([ { $project: { name: 1, email: 1, fullName: { $concat: ['$firstName', ' ', '$lastName'] } }} ]); // Exclude fields const results = await User.aggregate([ { $project: { password: 0, __v: 0 }} ]);
4. $sort - Sorting
javascript// Sort by age ascending const results = await User.aggregate([ { $sort: { age: 1 } } ]); // Multi-field sorting const results = await User.aggregate([ { $sort: { city: 1, age: -1 } } ]);
5. $limit and $skip - Pagination
javascript// Paginated query const page = 2; const pageSize = 10; const results = await User.aggregate([ { $skip: (page - 1) * pageSize }, { $limit: pageSize } ]);
6. $lookup - Join Queries
javascript// Join order data const results = await User.aggregate([ { $match: { _id: userId } }, { $lookup: { from: 'orders', localField: '_id', foreignField: 'userId', as: 'orders' }} ]); // Multiple joins const results = await User.aggregate([ { $lookup: { from: 'orders', localField: '_id', foreignField: 'userId', as: 'orders' }}, { $lookup: { from: 'reviews', localField: '_id', foreignField: 'userId', as: 'reviews' }} ]);
7. $unwind - Unwind Arrays
javascript// Unwind tags array const results = await User.aggregate([ { $unwind: '$tags' }, { $group: { _id: '$tags', count: { $sum: 1 } }} ]); // Preserve empty arrays const results = await User.aggregate([ { $unwind: { path: '$tags', preserveNullAndEmptyArrays: true } } ]);
Advanced Aggregation Operations
Array Operations
javascript// $push - Add to array const results = await User.aggregate([ { $group: { _id: '$city', users: { $push: '$name' } }} ]); // $addToSet - Add to set (deduplicate) const results = await User.aggregate([ { $group: { _id: '$city', uniqueTags: { $addToSet: '$tags' } }} ]); // $first and $last - Get first and last const results = await User.aggregate([ { $sort: { createdAt: 1 } }, { $group: { _id: '$city', firstUser: { $first: '$name' }, lastUser: { $last: '$name' } }} ]);
Conditional Operations
javascript// $cond - Conditional expression const results = await User.aggregate([ { $project: { name: 1, ageGroup: { $cond: { if: { $gte: ['$age', 18] }, then: 'adult', else: 'minor' } } }} ]); // $ifNull - Null handling const results = await User.aggregate([ { $project: { name: 1, displayName: { $ifNull: ['$displayName', '$name'] } }} ]);
Date Operations
javascript// Group by date const results = await Order.aggregate([ { $group: { _id: { year: { $year: '$createdAt' }, month: { $month: '$createdAt' }, day: { $dayOfMonth: '$createdAt' } }, total: { $sum: '$amount' }, count: { $sum: 1 } }} ]); // Date range query const results = await User.aggregate([ { $match: { createdAt: { $gte: new Date('2024-01-01'), $lt: new Date('2025-01-01') } }} ]);
Performance Optimization
Use Indexes
javascript// Use indexes in $match stage const results = await User.aggregate([ { $match: { email: 'john@example.com' } }, // Use index { $group: { _id: '$city', count: { $sum: 1 } } } ]);
Optimize Pipeline Order
javascript// Before optimization const results = await User.aggregate([ { $project: { name: 1, age: 1, city: 1 } }, { $match: { age: { $gte: 18 } } } ]); // After optimization - filter first then project const results = await User.aggregate([ { $match: { age: { $gte: 18 } } }, { $project: { name: 1, age: 1, city: 1 } } ]);
Use $facet for Parallel Processing
javascript// Execute multiple aggregation pipelines in parallel const results = await User.aggregate([ { $facet: { total: [{ $count: 'count' }], adults: [ { $match: { age: { $gte: 18 } } }, { $count: 'count' } ], byCity: [ { $group: { _id: '$city', count: { $sum: 1 } } } ] }} ]);
Practical Use Cases
Sales Statistics
javascriptconst salesStats = await Order.aggregate([ { $match: { createdAt: { $gte: new Date('2024-01-01'), $lt: new Date('2025-01-01') } }}, { $group: { _id: { year: { $year: '$createdAt' }, month: { $month: '$createdAt' } }, totalRevenue: { $sum: '$amount' }, orderCount: { $sum: 1 }, avgOrderValue: { $avg: '$amount' } }}, { $sort: { '_id.year': 1, '_id.month': 1 } } ]);
User Activity Analysis
javascriptconst userActivity = await User.aggregate([ { $lookup: { from: 'activities', localField: '_id', foreignField: 'userId', as: 'activities' }}, { $project: { name: 1, email: 1, activityCount: { $size: '$activities' }, lastActivity: { $max: '$activities.createdAt' } }}, { $sort: { activityCount: -1 } } ]);
Best Practices
- Use $match early: Reduce the amount of data processed
- Use indexes appropriately: Utilize indexes in $match stage
- Limit result count: Use $limit to avoid processing too much data
- Avoid deep nesting: Keep pipelines concise
- Use $facet: Process multiple aggregations in parallel
- Monitor performance: Use explain() to analyze aggregation performance
- Batch process large data: Use batch processing for large datasets