MongoDB $group and $project stages explained with examples
$group accumulator operators, $sum, $avg, $min, $max, $first, $last, $push, $addToSet, $project field inclusion and exclusion, computed fields
$group — collapse documents into summaries
$group collapses all documents sharing the same _id expression value into a single output document. Accumulator operators compute values across all documents in each group. Set _id: null to aggregate across the entire collection into one result.
// Group products by category and compute stats per group
const stats = await db.collection('products').aggregate([
{ $group: {
_id: '$category',
productCount: { $sum: 1 }, // count documents
avgPrice: { $avg: '$price' }, // mean price
minPrice: { $min: '$price' }, // lowest price
maxPrice: { $max: '$price' }, // highest price
allNames: { $push: '$name' }, // array with duplicates allowed
uniqueTags: { $addToSet: '$tag' } // deduplicated array
}}
]).toArray()$project — reshape and compute fields
$project includes, excludes, renames, and computes new fields using aggregation expressions. Unlike a find projection, $project can create entirely new computed fields using operators like $multiply, $concat, and $year.
db.collection('orders').aggregate([
{ $project: {
_id: 0,
orderId: '$_id',
discountedTotal: { $multiply: ['$total', 0.9] },
customerName: { $concat: ['$firstName', ' ', '$lastName'] },
orderYear: { $year: '$createdAt' },
isPremium: { $gt: ['$total', 500] }
}}
])When working with $group, remember that the _id field defines the grouping key — it is required and must not be confused with the document's _id field from the collection. Setting _id: null aggregates all documents in the pipeline into a single output document, which is useful for computing collection-wide totals like overall revenue or mean order value across all records.
