MongoDB $lookup Explained - How to Join Collections
- Introduction to MongoDB
- Installation & Database Creation
- CRUD Operations
- Embedded Documents and Arrays
- Validation Rules - Enforcing Structure in MongoDB
- Visualize MongoDB Relationships (Embedded vs Referenced)
- What Is an Index in MongoDB?
- Aggregation Pipeline Explained.
- $lookup Explained - How to Join Collections (You are here).
What Is $lookup?
The $lookup
stage in MongoDB lets you join data from another collection.
If you’ve worked with SQL, think of it as the MongoDB version of a LEFT OUTER JOIN.
Use it when you need to bring related data together, like combining movies with their reviews or users with their orders.
You use the .aggregate()
method in MongoDB and add $lookup
as one of the stages.
Basic Structure
Here’s the basic $lookup
syntax:
db.collection.aggregate([
{
$lookup: {
from: "<otherCollection>",
localField: "<field_in_current>",
foreignField: "<field_in_other>",
as: "<output_array_field>"
}
}
]);
-
from
- the collection you want to join -
localField
- the field in the current collection -
foreignField
- the field in the other collection -
as
- the name of the new array field that stores the results
Example 1: Join Movies with Reviews
We have two collections:
-
movie
-
reviews
Each review stores the movie_id it belongs to.
db.movie.aggregate([
{
$lookup: {
from: "reviews",
localField: "_id",
foreignField: "movie_id",
as: "movie_reviews"
}
}
]);
This query adds a movie_reviews
array to each movie document containing all matching reviews.
Example 2: Flatten the Result with $unwind
By default, $lookup
puts the results in an array.
If you want one document per review, use ``$unwind`:
db.movie.aggregate([
{
$lookup: {
from: "reviews",
localField: "_id",
foreignField: "movie_id",
as: "movie_reviews"
}
},
{ $unwind: "$movie_reviews" }
]);
- Why Use $unwind After $lookup?
By default, $lookup
adds the matching documents as an array field.
For example, movie_reviews
will contain a list of all reviews for each movie.
Sometimes this is exactly what you want.
Now each review appears as its own document linked to the movie.
Example 3: Join Across Multiple Levels
You can also chain ``$lookup` stages to bring in more collections. For example, join movie → reviews → users (to see who wrote each review):
db.movie.aggregate([
{
$lookup: {
from: "reviews",
localField: "_id",
foreignField: "movie_id",
as: "movie_reviews"
}
},
{ $unwind: "$movie_reviews" },
{
$lookup: {
from: "users",
localField: "movie_reviews.user_id",
foreignField: "_id",
as: "review_user"
}
},
{ $unwind: "$review_user" }
]);
This produces one document per movie-review-user combination.
Example 4: Using a Pipeline in $lookup
Since MongoDB 3.6, ``$lookup` can include a pipeline. This gives you more control, such as filtering the joined documents.
db.movie.aggregate([
{
$lookup: {
from: "reviews",
let: { movieId: "$_id" },
pipeline: [
{ $match: { $expr: { $eq: ["$movie_id", "$$movieId"] } } },
{ $project: { user_id: 1, rating: 1, _id: 0 } }
],
as: "filtered_reviews"
}
}
]);
Here, only selected fields (user_id, rating) are kept in filtered_reviews
.
Summary
The $lookup
stage lets you combine collections in MongoDB - just like a join in SQL.
You’ve learned how to:
- Use
$lookup
for simple joins - Flatten results with
$unwind
- Chain multiple
$lookup
stages - Use pipelines inside
$lookup
for advanced filtering
This is especially useful when visualized in DbSchema, where you can see relationships as diagrams.