- Node.js Tutorial
- NodeJS Home
- NodeJS Introduction
- NodeJS Setup
- NodeJS First App
- NodeJS REPL
- NodeJS Command Line
- NodeJS NPM
- NodeJS Callbacks
- NodeJS Events
- NodeJS Event-Loop
- NodeJS Event-Emitter
- NodeJS Global-Objects
- NodeJS Console
- NodeJS Process
- NodeJS Buffers
- NodeJS Streams
- Node.js File Handling
- Node.js File System
- Node.js Read/Write File
- Working with folders in Node.js
- HTTP and Networking
- Node.js HTTP Module
- Anatomy of an HTTP Transaction
- Node.js MongoDB
- MongoDB Get Started
- MongoDB Create Database
- MongoDB Create Collection
- MongoDB Insert
- MongoDB Find
- MongoDB Query
- MongoDB Sort
- MongoDB Delete
- MongoDB Update
- MongoDB Limit
- MongoDB Join
- Node.js MySQL
- MySQL Get Started
- MySQL Create Database
- MySQL Create Table
- MySQL Insert Into
- MySQL Select From
- MySQL Where
- MySQL Order By
- MySQL Delete
- MySQL Update
- MySQL Join
- Node.js Modules
- Node.js Modules
- Node.js Built-in Modules
- Node.js Utility Modules
- Node.js Web Module
- Node.js Advanced
- Node.js Debugger
- Node.js Scaling Application
- Node.js Packaging
- Node.js Express Framework
- Node.js RESTFul API
- Node.js Useful Resources
- Node.js Useful Resources
- Node.js Discussion
Node.js MongoDB Join
In the world of relational databases (like MySQL or PostgreSQL), joining tables is a daily task. However, MongoDB is a NoSQL document database, which encourages "embedding" data directly within documents. That said, there are many real-world scenarios where data must live in separate collections—such as separating Users from Orders to keep documents from growing too large.
To handle these relationships, MongoDB provides the $lookup operator. This operator functions within an Aggregation Pipeline, allowing you to perform a "left outer join" to pull in related documents from another collection based on a shared key.
$lookup or $match) transforms the data before passing it to the next stage.
Key Features of MongoDB Join
- Aggregation Framework: Unlike a simple
find()query, joins require theaggregatemethod, which is designed for complex data processing. - Left Outer Join: By default,
$lookupkeeps all documents from your primary collection, even if no matching documents are found in the joined collection. - Flexible Results: The joined data is returned as an array within the original document, preserving the hierarchical nature of JSON/BSON.
$lookup when necessary. If you frequently need to join two collections, consider if "embedding" the data (storing it inside the same document) would improve your application's performance.
Step 1 Prerequisites
Before writing your join logic, ensure you have the MongoDB driver installed in your Node.js project. You should also have a local or cloud-based (Atlas) MongoDB instance running.
npm install mongodb
Step 2 Using $lookup to Join Collections
The $lookup operator is the heart of the join. It requires four main parameters:
from: The target collection you want to pull data from.localField: The attribute in your current collection that links to the other collection.foreignField: The attribute in the target collection that matches thelocalField.as: The name of the new array field that will hold the joined results.
Example Code
Let's look at a common e-commerce scenario. We have a users collection and an orders collection. We want to find all users and attach their specific orders to their profile.
const { MongoClient } = require('mongodb');
// Connection URL and Database Name
const url = 'mongodb://127.0.0.1:27017';
const dbName = 'mydatabase';
async function joinCollections() {
const client = new MongoClient(url);
try {
await client.connect();
const db = client.db(dbName);
const usersCollection = db.collection('users');
// We start the "assembly line" on the users collection
const result = await usersCollection.aggregate([
{
$lookup: {
from: 'orders', // Join with the orders collection
localField: '_id', // Use the User's ID
foreignField: 'userId', // Match it against the userId in orders
as: 'userOrders' // Output the list of orders as "userOrders"
}
}
]).toArray();
console.log(JSON.stringify(result, null, 2));
} finally {
await client.close();
}
}
joinCollections().catch(console.error);
localField and foreignField match. If one is a String and the other is an ObjectId, the join will fail to find matches and return an empty array.
Output:
[
{
"_id": "user1",
"name": "Alice",
"userOrders": [
{ "orderId": "order1", "amount": 100, "userId": "user1" },
{ "orderId": "order2", "amount": 150, "userId": "user1" }
]
},
{
"_id": "user2",
"name": "Bob",
"userOrders": [
{ "orderId": "order3", "amount": 200, "userId": "user2" }
]
}
]
Step 3 Filtering with $lookup
Often, you don't just want to join data; you want to find specific records within that join. Because $lookup returns an array, we use $unwind to flatten that array into individual documents, making it easier to filter using $match.
Example Code
In this example, we only want to see orders where the purchase amount is greater than 100.
async function joinAndFilter() {
const client = new MongoClient(url);
try {
await client.connect();
const db = client.db(dbName);
const usersCollection = db.collection('users');
const result = await usersCollection.aggregate([
{
$lookup: {
from: 'orders',
localField: '_id',
foreignField: 'userId',
as: 'userOrders'
}
},
{
// $unwind breaks the array into separate documents for each order
$unwind: '$userOrders'
},
{
// Now we can filter the flattened documents
$match: { 'userOrders.amount': { $gt: 100 } }
}
]).toArray();
console.log(result);
} finally {
await client.close();
}
}
joinAndFilter().catch(console.error);
$unwind will remove any documents that have an empty join array. If a user has no orders, they will disappear from your results after $unwind. Use preserveNullAndEmptyArrays: true if you want to keep them.
Step 4 Using $lookup with Multiple Joins
In complex applications, you might need to "chain" joins. For example, joining Users to Orders, and then joining those Orders to a Products collection to see what was actually bought.
Example Code
async function multipleJoins() {
const client = new MongoClient(url);
try {
await client.connect();
const db = client.db(dbName);
const usersCollection = db.collection('users');
const result = await usersCollection.aggregate([
{
$lookup: {
from: 'orders',
localField: '_id',
foreignField: 'userId',
as: 'userOrders'
}
},
{ $unwind: '$userOrders' },
{
$lookup: {
from: 'products',
localField: 'userOrders.productId', // Link the order to a product
foreignField: '_id',
as: 'productDetails'
}
}
]).toArray();
console.log(result);
} finally {
await client.close();
}
}
multipleJoins().catch(console.error);
foreignField in your target collection is indexed. Without an index, MongoDB has to scan every document in the target collection for every document in the source collection, which can be extremely slow.
Summary
While MongoDB is designed for document nesting, the $lookup operator provides the necessary power to handle relational data when your schema requires it. By mastering the Aggregation Pipeline, you can perform single joins, filter nested results, and even chain multiple collections together. Just remember to keep performance in mind by using indexes and only joining data when embedding isn't a viable option.