Chapter 15: MongoDB Aggregation $project
1. What does $project really do? (Teacher explanation — no jargon first)
$project lets you:
- Decide which fields you want to keep in the output documents
- Decide which fields you want to remove
- Rename fields
- Create brand new computed / derived fields using expressions
- Reshape nested documents
- Include / exclude the _id field (very important!)
In simple words:
After previous stages have done filtering, grouping, sorting… $project is the final makeup artist — it decides what the output document should look like before it reaches your application or the next stage.
2. Two Main Styles of $project (Very Important to Understand)
There are two completely different ways people write $project — and mixing them up causes 90% of beginner confusion.
| Style | Syntax looks like | What it means | When to use it |
|---|---|---|---|
| Inclusion / Exclusion | { fieldA: 1, fieldB: 1, _id: 0 } | 1 = keep this field, 0 = remove this field | Quick clean-up of unwanted fields |
| Reshaping / Computed | { newName: “$oldName”, calc: { $add: […] } } | Expressions, new fields, renaming | Almost every real-world pipeline |
Rule teacher screams three times:
You cannot mix inclusion/exclusion style (0 and 1) with expression style in the same $project stage (except for _id).
Wrong (MongoDB will throw error in most versions):
|
0 1 2 3 4 5 6 7 |
{ $project: { title: 1, year: 1, doubledRevenue: { $multiply: ["$revenue", 2] } } } // → error: cannot mix inclusion map with computed fields |
Correct ways:
- Use only 0/1 style → simple keep/remove
- Use expression style → create/rename/compute (most powerful & common)
3. Hands-on Examples — Using Our Movie Collection
|
0 1 2 3 4 5 6 |
use movieAnalytics2026 |
Example 1: Inclusion / Exclusion style (simple cleanup)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
db.movies.aggregate([ { $match: { year: { $gte: 2022 } } }, { $project: { title: 1, // keep rating: 1, // keep year: 1, // keep revenue: 0, // remove comments: 0, // remove _id: 0 // hide _id } } ]) |
→ Output documents contain only: title, rating, year
Example 2: Expression style — Rename + create new fields
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
db.movies.aggregate([ { $project: { movieTitle: "$title", // rename releaseYear: "$year", // rename ratingOutOf10: "$rating", // rename (clarity) isBlockbuster: { $gte: ["$rating", 8.0] }, // new boolean field revenueInCrores: "$revenue", // rename doubledRevenue: { $multiply: ["$revenue", 2] }, _id: 0 } } ]) |
Example 3: Real analytics — After $group + computed fields
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
db.movies.aggregate([ { $unwind: "$genres" }, { $group: { _id: "$genres", count: { $sum: 1 }, avgRating: { $avg: "$rating" }, totalRevenue: { $sum: "$revenue" }, movieList: { $push: "$title" } } }, { $project: { genre: "$_id", // rename _id to genre moviesInGenre: "$count", averageRating: { $round: ["$avgRating", 2] }, totalBoxOfficeCr: "$totalRevenue", topMovies: { $slice: ["$movieList", 3] }, // first 3 movies only isPopularGenre: { $gte: ["$count", 2] }, // new computed flag _id: 0 } }, { $sort: { moviesInGenre: -1 } }, { $limit: 5 } ]) |
Example 4: Nested reshaping + conditional logic
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
db.movies.aggregate([ { $project: { title: 1, year: 1, rating: 1, performance: { revenueCr: "$revenue", verdict: { $switch: { branches: [ { case: { $gte: ["$rating", 8.5] }, then: "Super Hit" }, { case: { $gte: ["$rating", 7.5] }, then: "Hit" }, { case: { $gte: ["$rating", 6.0] }, then: "Average" } ], default: "Flop" } } }, _id: 0 } } ]) |
4. Quick Reference Table – $project Cheat Sheet
| Goal | Style used | Typical syntax snippet | Notes / Tip |
|---|---|---|---|
| Remove _id & few unwanted fields | Inclusion | { title: 1, rating: 1, _id: 0, comments: 0 } | Fast & simple |
| Rename fields | Expression | { movieName: “$title”, relYear: “$year” } | Very common |
| Create boolean flag | Expression | { isHighRated: { $gte: [“$rating”, 8] } } | Great for UI filters |
| Round numbers | Expression | { avgRating: { $round: [“$avgRating”, 1] } } | Clean presentation |
| Conditional new field | Expression | { $cond: { if: …, then: …, else: … } } or $switch | Replaces CASE WHEN in SQL |
| Slice / take subset of array | Expression | { recentComments: { $slice: [“$comments”, -3] } } | Last 3 elements (-3) |
| Concatenate strings | Expression | { fullInfo: { $concat: [“$title”, ” (“, { $toString: “$year” }, “)” ] } } | Display friendly |
5. Mini Exercise – Try Right Now!
- Show only title, rating, year — hide everything else (inclusion style)
- Create a pipeline that shows:
- movieTitle
- ratingStars (multiply rating by 1 → same, just rename)
- isSuperhit (true if rating ≥ 8.3)
- boxOfficeMessage = “₹” + revenue + ” Cr” (use $concat)
- After grouping by country — project country name, total revenue, avg rating rounded to 1 decimal
Understood beta? $project is like the final filter on what your boss / user actually sees — spend time mastering expressions here and your reports become clean, beautiful, and useful.
Next class — what do you want?
- $addFields vs $project (when to use which)
- $project inside $lookup (joined data reshaping)
- String, date, math operators inside $project
- Or continue building a full “Movie Insights Dashboard” pipeline?
Tell me — class is still full of energy! 🚀❤️
Any confusion with $project? Ask anything — we’ll fix it together 😄
