“I had no idea how powerful MongoDB Aggregation was until I had to filter videos, join users, sort by views, and paginate them. Let me show you how I did it in my YouTube-style MERN app.”
Introduction
When I started building a YouTube-like video app using MERN (MongoDB, Express, React, Node), everything was going great until it wasn’t.
I had:
- Videos stored in MongoDB
- Users uploading videos
- People liking, commenting, and watching videos
But it hit hard when I needed to:
- Show videos with uploader info (user data)
- Sort them by most viewed or most liked
- Paginate the results (page 1, page 2...)
- Count how many likes a video had
- Filter videos by search queries or tags That’s when I realized MongoDB Aggregation Framework isn’t just for analytics. It’s the real deal when building full-stack apps.
Let me walk you through how I used it.
What Is MongoDB Aggregation?
Think of your MongoDB documents as a river of water.
Aggregation is like a water filter machine made of multiple pipes (called stages) where each pipe processes, cleans, or transforms the data before sending it to the next.
Every stage takes the water (data), does something (like filtering, sorting, or grouping), and passes it on.
Each stage is like:
- $match: Filters the data
- $project: Picks specific fields
- $lookup: Joins with another collection
- $group: Combines data for totals/averages
- $sort: Reorders data
- $skip & $limit: For pagination
Etc
Real Scenario: My YouTube Clone Setup
Video Collection
{
_id: ObjectId("abc"),
title: "My First Vlog",
description: "...",
owner: ObjectId("user123"),
views: 134,
likes: 20,
tags: ["vlog", "life"],
createdAt: ...
}
User Collection
{
_id: ObjectId("user123"),
name: "John Doe",
avatar: "avatar.jpg"
}
Like Collection
{
_id: ObjectId("like456"),
video: ObjectId("abc"),
likedBy: ObjectId("user456")
}
Use Case 1: Show All Videos With Uploader Info
Problem:
I needed to show videos along with who uploaded them (name + avatar). In SQL, I’d JOIN the user table. In MongoDB? Use $lookup.
Solution:
const videos = await Video.aggregate([
{
$lookup: {
from: "users", // collection to join
localField: "owner", // field in video
foreignField: "_id", // field in user
as: "uploader"
}
},
{
$unwind: "$uploader"
},
{
$project: {
title: 1,
views: 1,
"uploader.name": 1,
"uploader.avatar": 1
}
}
]);
What This Does:
- Joins the users collection to every video
- Flattens the uploader array using $unwind
- Projects only the fields I need
Use Case 2: Count Total Likes Per Video
In a separate likes collection, I wanted to show number of likes each video has.
const likedVideos = await Like.aggregate([
{
$group: {
_id: "$video",
totalLikes: { $sum: 1 }
}
},
{
$lookup: {
from: "videos",
localField: "_id",
foreignField: "_id",
as: "video"
}
},
{ $unwind: "$video" },
{
$project: {
totalLikes: 1,
title: "$video.title",
views: "$video.views"
}
}
]);
Use Case 3: Search + Pagination + Sort Together
imagine a search screen with:
- Search by title
- Page 2
- Limit 5 per page
- Sort by views descending
const page = 2;
const limit = 5;
const search = "vlog";
const videos = await Video.aggregate([
{
$match: {
title: { $regex: search, $options: "i" }
}
},
{
$sort: { views: -1 }
},
{
$skip: (page - 1) * limit
},
{
$limit: limit
},
{
$project: {
title: 1,
views: 1
}
}
]);
Common Mistakes & Tips
1. Always Convert IDs for $match
{ _id: mongoose.Types.ObjectId(videoId) }
2.Always Convert limit (and page) in Aggregation
$limit: parseInt(limit)
$skip: (parseInt(page) - 1) * parseInt(limit)
2. Don’t Over-Nest $lookup
Avoid deeply nested joins MongoDB isn’t a relational DB. Normalize lightly.
4. Pagination = $skip + $limit (Not .slice()!)
Don’t slice in memory do it inside aggregation.
You Got Questions
🙋 Got questions? Stuck on your own aggregation logic? Drop a comment below ,I’ll try to help!
Top comments (0)