How to Perform JOIN in MongoDB Using $lookup

By Guilherme Luiz Maia Pinto
Picture of the author
Published on
MongoDB $lookup Join Banner

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 profiles
  • orders — orders referencing customers via customerId

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.

Stay Tuned

Want to become a Software Engineer pro?
The best articles and links related to web development are delivered once a week to your inbox.