The MongoDB Aggregation Pipeline by Example

A
Author
02 Aug, 2026 2 min read 1.37K views

The aggregation pipeline processes documents through a series of stages, each transforming the output of the previous one. It's MongoDB's answer to SQL's GROUP BY, joins and reporting queries.

Revenue per day

const revenue = await orders.aggregate([
  { $match: { status: 'paid', createdAt: { $gte: new Date('2026-10-01') } } },
  { $unwind: '$items' },                                // one document per item
  {
    $group: {
      _id: { $dateToString: { format: '%Y-%m-%d', date: '$createdAt' } },
      revenue: { $sum: { $multiply: ['$items.qty', '$items.price'] } },
      orders: { $addToSet: '$_id' },
    },
  },
  { $project: { _id: 0, day: '$_id', revenue: 1, orderCount: { $size: '$orders' } } },
  { $sort: { day: 1 } },
]).toArray();

Common stages

  • $match - filter (like WHERE). Put it first so indexes can be used.
  • $group - aggregate with $sum, $avg, $min, $max, $push.
  • $project / $addFields - reshape documents and compute fields.
  • $unwind - turn an array into one document per element.
  • $sort, $limit, $skip.
  • $lookup - join with another collection.

Joining with $lookup

const ordersWithCustomer = await orders.aggregate([
  { $match: { status: 'paid' } },
  { $limit: 20 },
  {
    $lookup: {
      from: 'customers',
      localField: 'customerId',
      foreignField: '_id',
      as: 'customer',
      pipeline: [{ $project: { name: 1, email: 1 } }],
    },
  },
  { $unwind: '$customer' },
]).toArray();

Several reports in one query with $facet

const [stats] = await products.aggregate([
  { $match: { active: true } },
  {
    $facet: {
      byCategory: [{ $group: { _id: '$category', count: { $sum: 1 } } }],
      priceStats: [{ $group: { _id: null, avg: { $avg: '$price' }, max: { $max: '$price' } } }],
      newest: [{ $sort: { createdAt: -1 } }, { $limit: 5 }, { $project: { name: 1 } }],
    },
  },
]).toArray();

Stages each have memory limits; large sorts and groups may need allowDiskUse. Filter early, project away unneeded fields, and run heavy analytics on a secondary or analytics node.

Was this article helpful?

Comments

No comments yet. Have a question or a tip? Be the first to share it.

Leave a comment

Comments are reviewed before they appear.