We will first introduce the structure and relationships between each collections, then we will be going through how to use $lookup in mongodb aggregation to get our desired output
- User Collection
- Expenses Category Collection
- Expenses Collection
- Relationship between each Collections
- Aggregation $lookup
- Clean up Response Output by using $project
Documents in User Collection
/* 1 */
{
"_id" : ObjectId("6173e5fe7fc8622cbc56a108"),
"name" : "Jeff",
"age" : 20,
"ref_id" : "457935c0-33ed-11ec-aac1-dd97bcc28a8a",
"__v" : 0
}
/* 2 */
{
"_id" : ObjectId("6173e6067fc8622cbc56a10a"),
"name" : "Jason",
"age" : 25,
"ref_id" : "4a4bcc70-33ed-11ec-aac1-dd97bcc28a8a",
"__v" : 0
}Documents in Expenses Category Collection
/* 1 */
{
"_id" : ObjectId("6173e6577fc8622cbc56a10c"),
"label" : "Food",
"user_ref_id" : "457935c0-33ed-11ec-aac1-dd97bcc28a8a",
"ref_id" : "7a27b0d0-33ed-11ec-aac1-dd97bcc28a8a",
"__v" : 0
}
/* 2 */
{
"_id" : ObjectId("6173e6677fc8622cbc56a10e"),
"label" : "Entertainment",
"user_ref_id" : "457935c0-33ed-11ec-aac1-dd97bcc28a8a",
"ref_id" : "84021f00-33ed-11ec-aac1-dd97bcc28a8a",
"__v" : 0
}
/* 3 */
{
"_id" : ObjectId("6173e69d7fc8622cbc56a112"),
"label" : "Transportation",
"user_ref_id" : "4a4bcc70-33ed-11ec-aac1-dd97bcc28a8a",
"ref_id" : "a3e28580-33ed-11ec-aac1-dd97bcc28a8a",
"__v" : 0
}
/* 4 */
{
"_id" : ObjectId("6173e6d07fc8622cbc56a114"),
"label" : "Monthly Installment",
"user_ref_id" : "4a4bcc70-33ed-11ec-aac1-dd97bcc28a8a",
"ref_id" : "c2a40e80-33ed-11ec-aac1-dd97bcc28a8a",
"__v" : 0
}Documents in Expenses Collection
/* 1 */
{
"_id" : ObjectId("6173e7677fc8622cbc56a116"),
"label" : "Mc Donald",
"value" : 11.5,
"expenses_category_ref_id" : "7a27b0d0-33ed-11ec-aac1-dd97bcc28a8a",
"ref_id" : "1cacea50-33ee-11ec-aac1-dd97bcc28a8a",
"__v" : 0
}
/* 2 */
{
"_id" : ObjectId("6173e7767fc8622cbc56a118"),
"label" : "KFC",
"value" : 15,
"expenses_category_ref_id" : "7a27b0d0-33ed-11ec-aac1-dd97bcc28a8a",
"ref_id" : "25358420-33ee-11ec-aac1-dd97bcc28a8a",
"__v" : 0
}
/* 3 */
{
"_id" : ObjectId("6173e78a7fc8622cbc56a11a"),
"label" : "Sushi King",
"value" : 35,
"expenses_category_ref_id" : "7a27b0d0-33ed-11ec-aac1-dd97bcc28a8a",
"ref_id" : "312787b0-33ee-11ec-aac1-dd97bcc28a8a",
"__v" : 0
}
/* 4 */
{
"_id" : ObjectId("6173e7e07fc8622cbc56a11c"),
"label" : "karaoke",
"value" : 45,
"expenses_category_ref_id" : "84021f00-33ed-11ec-aac1-dd97bcc28a8a",
"ref_id" : "649c2330-33ee-11ec-aac1-dd97bcc28a8a",
"__v" : 0
}
/* 5 */
{
"_id" : ObjectId("6173e7f87fc8622cbc56a11e"),
"label" : "Petrol Fee",
"value" : 65,
"expenses_category_ref_id" : "a3e28580-33ed-11ec-aac1-dd97bcc28a8a",
"ref_id" : "72f1b940-33ee-11ec-aac1-dd97bcc28a8a",
"__v" : 0
}
/* 6 */
{
"_id" : ObjectId("6173e8047fc8622cbc56a120"),
"label" : "Car Maintenance Service",
"value" : 150,
"expenses_category_ref_id" : "a3e28580-33ed-11ec-aac1-dd97bcc28a8a",
"ref_id" : "7a206860-33ee-11ec-aac1-dd97bcc28a8a",
"__v" : 0
}
/* 7 */
{
"_id" : ObjectId("6173e8147fc8622cbc56a122"),
"label" : "House Loan",
"value" : 1500,
"expenses_category_ref_id" : "c2a40e80-33ed-11ec-aac1-dd97bcc28a8a",
"ref_id" : "83b31f30-33ee-11ec-aac1-dd97bcc28a8a",
"__v" : 0
}
/* 8 */
{
"_id" : ObjectId("6173e8237fc8622cbc56a124"),
"label" : "Insurance",
"value" : 230,
"expenses_category_ref_id" : "c2a40e80-33ed-11ec-aac1-dd97bcc28a8a",
"ref_id" : "8cc22710-33ee-11ec-aac1-dd97bcc28a8a",
"__v" : 0
}Relationship between each Collections
As we can observe the diagram above, collection Expenses Category is actually having child-to-parent relationship where it stored the user-ref-id field which is equivalent to ref-id in User collection.
Moving on, we also have collection Expenses which also having the child-to-parent relationship where it stored the expenses-category-ref-id field which is equivalent to ref-id in Expenses Category collection.
Aggregation $lookup
Controller below is belongs to the route http://localhost:3000/user/:id where the parameter id is the ref_id of the User
|
|
Result
This case, we are using the 457935c0-33ed-11ec-aac1-dd97bcc28a8a ref_id from the user called Jeff to pass as the route parameter.
The expected result will be => [response.json]
Explanations
- Import all the necessary Models such as User, Expenses Category and Expenses
- Get and access the route parameter
:idwhich is equivalent to the Userref_id - Apply aggregation method from the
User collection - [Code Line 10 ~ 14]
- Find the relavant document by using $match operator and comparing the route param
:idwith ref_id fromUser collection - This process is equivalent to
User.findOne({ref_id: req.param.id})
- Find the relavant document by using $match operator and comparing the route param
$lookupsyntax- from: < the target collection you want to join >
- let: < declare custom variable from your current Model. “In this case it’s referring User collection” >
- pipeline < Compare and query the condition to get desired output >
- as < specific the field name of the array output >
- [Code Line 15 ~ 24]
- First, we apply
$lookupoperator to join theexpenses-category - Since,
expenses-categorymodel actually storinguser_ref_id. We can then declare the variable calleduserRefIdwhich is referring to the Userref_idand compare them later on - In order to use the custom variable we have declared earlier, we need to use the operator called $expr
- use
$matchoperator to compare and filter out the result based onuserRefIdvariable with theuser_ref_idfromexpenses-categorycollection underpipelineoperation - specific the query array output field name called
expenses-categoriesin Line 40
- First, we apply
- [Code Line 25 ~ 38]
- Since
expensescollection is nested insideexpenses-categorycollection andexpenses-categorycollection is nested insideusercollection. We then now need to$lookupone more round fromexpensescollection toexpenses-categorycollection before we moved out the operation - Apply
$lookupoperator to join theexpensescollection - Since,
expensesmodel actually storingexpenses_category_ref_id. We can then declare the variable known ascategoryRefIdwhich is referring to the Expenses Categoryref_idand compare them later on - use
$matchoperator to compare and filter out the result based oncategoryRefIdvariable with theexpenses_category_ref_idfromexpensescollection underpipelineoperation - specific the query array output field name called
expenses
- Since
- As now we know the ref_id is actually unique key in every documents while output data type from aggregation is Array. So we can just return the first element of the array or return empty object if the
ref_iddoesn’t existed.
Clean up Response Output by using $project
If you have seen the output response from the query above response.json. You may realize there are actually some unecessary fields we can skip or remove from our query.
Let’s see how we could handle it by using $project operator.
|
|
Result
The expected result will be => [filtered_response.json]
Explanations
- The output response is much cleaner after we used
$projectoperator so remove and clean up the fields 0is equals to hide while1is equals to show- [Code Line 35 ~ 42] Expenses Collection
- Please take note that the
_idfield will be always show by default. Therefore, we can always assign0to hide this particular field. - Then we can assign
1to only show the necessary fields such asref_id,labelandvalue
- Please take note that the
- The same concept applies to
Expenses Category Collectionin [Code Line 47 ~ 52] andUser Collectionin [Code Line 58 ~ 63]