How to Perform JOIN in MongoDB Using $lookup

- Published on

MongoDB is a document‑oriented NoSQL database. Although it doesn’t have native relational JOINs, the aggregation framework provides $lookup
to combine documents across collections—similar to SQL joins. This guide shows $lookup
essentials with practical examples.
Collection Structure
Two collections:
customers
— customer profilesorders
— orders referencing customers viacustomerId
Example documents:
// customers
{ "_id": ObjectId("64...01"), "customerId": 1, "name": "Alice", "city": "NY" }
{ "_id": ObjectId("64...02"), "customerId": 2, "name": "Bob", "city": "SF" }
// orders
{ "_id": ObjectId("75...01"), "orderId": 101, "customerId": 1, "totalAmount": 150 }
{ "_id": ObjectId("75...02"), "orderId": 102, "customerId": 2, "totalAmount": 80 }
Basic $lookup
(join orders → customers)
List all orders with their customer info:
db.orders.aggregate([
{
$lookup: {
from: "customers", // collection to join
localField: "customerId", // orders.customerId
foreignField: "customerId", // customers.customerId
as: "customerData" // array field with matches
}
}
])
Result shape (simplified):
{
"orderId": 101,
"customerId": 1,
"totalAmount": 150,
"customerData": [ { "customerId": 1, "name": "Alice", "city": "NY" } ]
}
Flattening with $unwind
If you prefer a flat document per order, unwind the joined array:
db.orders.aggregate([
{
$lookup: {
from: "customers",
localField: "customerId",
foreignField: "customerId",
as: "customerData"
}
},
{ $unwind: "$customerData" },
{
$project: {
_id: 0,
orderId: 1,
totalAmount: 1,
customerId: 1,
customerName: "$customerData.name",
customerCity: "$customerData.city"
}
}
])
$lookup
with Additional Filters
Combine $lookup
with $match
to filter results (e.g., orders over 100):
db.orders.aggregate([
{ $match: { totalAmount: { $gt: 100 } } },
{
$lookup: {
from: "customers",
localField: "customerId",
foreignField: "customerId",
as: "customerData"
}
},
{ $unwind: "$customerData" },
{
$project: {
_id: 0,
orderId: 1,
totalAmount: 1,
customer: {
name: "$customerData.name",
city: "$customerData.city"
}
}
}
])
Tip: For complex joins, consider the pipeline form of $lookup
(with let
and inner $match
using $expr
).
Conclusion
$lookup
lets you join data across collections without fully denormalizing your schema. Use it with $unwind
, $project
, and $match
to shape and filter results for reports and APIs.