Reports, dashboards and any server-side data reshaping use aggregation instead of shipping documents to the app.
aggregate([stage, stage, ...]) passes documents through a pipeline of stages, each feeding the next, like a Unix pipe or chained array methods.
$matchfilters (same syntax as find); put it first so it can use indexes,$groupgroups by_idand computes accumulators:$sum,$avg,$min,$max,$count,$push,$addToSet,$first,$projector$setreshape documents and compute fields with expressions such as$multiply,$concat,$dateToString,$cond,$sort,$limit,$skip,$count,$unwindand$lookup(next lessons).
Inside expressions, "$field" (a string starting with $) refers to a field's value, while plain strings are literals. Grouping by null produces a single total over all documents.
The optimizer reorders and merges stages where safe, for example moving a $match before a $project, but write pipelines in the efficient order anyway: filter early, project away big fields early, sort on indexed fields before grouping when possible.
Each stage may use up to 100 MB of RAM; larger sorts and groups spill to disk (allowed by default since MongoDB 6.0). $out and $merge write results to a collection, useful for materialized summaries.
Example
db.sales.insertMany([
{ region: "north", item: "pen", qty: 10, price: 1.5, day: ISODate("2026-10-01") },
{ region: "north", item: "pad", qty: 2, price: 4, day: ISODate("2026-10-01") },
{ region: "south", item: "pen", qty: 5, price: 1.5, day: ISODate("2026-10-02") },
{ region: "south", item: "ink", qty: 1, price: 9, day: ISODate("2026-10-03") }
])
db.sales.aggregate([
{ $match: { day: { $gte: ISODate("2026-10-01") } } },
{ $set: { revenue: { $multiply: ["$qty", "$price"] } } },
{ $group: { _id: "$region", revenue: { $sum: "$revenue" }, orders: { $count: {} } } },
{ $sort: { revenue: -1 } }
])
// [ { _id: 'north', revenue: 23, orders: 2 },
// { _id: 'south', revenue: 16.5, orders: 2 } ]Remember
- A pipeline is an array of stages; output of one is input to the next
- "$field" references a value; $group needs an _id
- $match early so indexes can be used
- Stages have a 100 MB memory limit and spill to disk when allowed
Watch out for
- Writing "qty" instead of "$qty" in an expression: you get the literal string, not the field value.