I'm working on an eCommerce platform in MERN stack. In my project, I have three schemas: User, Product, and Cart. The Cart schema references both User and Product schemas.
Below is my simplified schema structures:
cartModel.js
const mongoose = require("mongoose");
const { Schema, model } = mongoose;
const cartSchema = new Schema(
{
user: { type: Schema.Types.ObjectId, ref: "User", required: true },
product: { type: Schema.Types.ObjectId, ref: "Product", required: true },
unit: { type: Number, required: true },
},
{ timestamps: true, versionKey: false }
);
const cartModel = model("Cart", cartSchema);
module.exports = cartModel;
productModel.js
const mongoose = require("mongoose");
const { Schema, model } = mongoose;
const productSchema = new Schema(
{
title: String,
short_des: String,
price: Number,
discount_price: Number,
image: String,
stock: String,
star: String,
},
{ timestamps: true, versionKey: false }
);
const productModel = model("Product", productSchema);
module.exports = productModel;
userModel.js
const mongoose = require("mongoose");
const { Schema, model } = mongoose;
const userSchema = new Schema(
{
name: { type: String, required: true },
email: { type: String, unique: true, required: true },
password: { type: String, required: true },
},
{ timestamps: true, versionKey: false }
);
const userModel = model("User", userSchema);
module.exports = userModel;
My problem is, In my cart controller, I am to trying to define a query to join the Cart, User, and Product models, and trying to extract specific information but my query isn't working as expected.
I need to extract below information for each product grouped by user:
And I trying to return the result in an array so that I can use map method it in my react frontend to display data.
cartController.js
exports.cartList = async (req, res) => {
try {
let userId = new mongoose.Types.ObjectId(req.user._id);
let result = await cartModel.aggregate([
{ $match: { user: userId } },
{
$lookup: {
from: "users", // Name of the User model collection
localField: "user",
foreignField: "_id",
as: "userDetails",
},
},
{
$lookup: {
from: "products", // Name of the Product model collection
localField: "product",
foreignField: "_id",
as: "productDetails",
},
},
{
$group: {
_id: "$user",
},
},
]);
res.status(200).json({ status: true, data: result });
} catch (error) {
res.status(200).json({ status: false, data: error.message });
}
};
A sample cart model data is below
{
"_id": {
"$oid": "6592be024fc8de3f1771a9e1"
},
"product": {
"$oid": "657fab1586f102e3657bf034"
},
"user": {
"$oid": "6559696bfa9b7df357161a81"
},
"createdAt": {
"$date": "2024-01-01T13:28:34.452Z"
},
"unit": 4,
"updatedAt": {
"$date": "2024-01-01T13:28:46.865Z"
}
},
{
"_id": {
"$oid": "6592dc014fc8de3f1771bc3f"
},
"product": {
"$oid": "65801d5e2c366e8773ada21f"
},
"user": {
"$oid": "6559696bfa9b7df357161a81"
},
"createdAt": {
"$date": "2024-01-01T15:36:33.420Z"
},
"unit": 3,
"updatedAt": {
"$date": "2024-01-01T15:36:37.161Z"
}
}
Could someone provide guidance or an example of an aggregation query that retrieves the specified information from multiple collections (Cart, User, and Product) in a format suitable for mapping in React? Any help or suggestions would be greatly appreciated. Thank you!
You could do the following:
Option 1:
This would give you an array of 1 object grouped by the user which would have a name
, email
and carts
property. The carts
property would be all the carts the user has, each with the specific fields you need. That way you can map the carts
.
const result = await cartModel.aggregate([
{
$match: {
user: userId
}
},
{
$lookup: {
from: "users",
localField: "user",
foreignField: "_id",
as: "userDetails"
}
},
{
$unwind: "$userDetails"
},
{
$lookup: {
from: "products",
localField: "product",
foreignField: "_id",
as: "productDetails"
}
},
{
$unwind: "$productDetails"
},
{
$group: {
_id: "$userDetails._id",
name: {
$first: "$userDetails.name"
},
email: {
$first: "$userDetails.email"
},
carts: {
$push: {
cart_id: "$_id",
product: "$productDetails",
unit: "$unit"
}
}
}
},
{
$project: {
_id: 0,
name: 1,
email: 1,
unit: 1,
"carts.cart_id": 1,
"carts.product": {
price: 1,
title: 1
}
}
}
])
See HERE for a working example.
Option 2:
This would give you an array of 1 object grouped by the user but has a single carts
property. This would be an array of all the carts the user has but this time each cart would contain the user details in case you need them while looping on the front-end.
const result = await cartModel.aggregate([
{
$match: {
user: userId
}
},
{
$lookup: {
from: "users",
localField: "user",
foreignField: "_id",
as: "userDetails"
}
},
{
$unwind: "$userDetails"
},
{
$lookup: {
from: "products",
localField: "product",
foreignField: "_id",
as: "productDetails"
}
},
{
$unwind: "$productDetails"
},
{
$group: {
_id: "$userDetails._id",
carts: {
$push: {
cart_id: "$_id",
product: "$productDetails",
unit: "$unit",
user: "$userDetails"
}
}
}
},
{
$project: {
_id: 0,
unit: 1,
"carts.cart_id": 1,
"carts.product": {
price: 1,
title: 1
},
"carts.user": {
email: 1,
name: 1
}
}
}
])
See HERE for a working example.
Option 3:
Since you are using reference docs for user
and product
properties you can just use Model.populate()
like so:
const result = await cartModel.find({user: userId})
.populate('user', 'name email -_id')
.populate('product', 'title price -_id')
.select('unit');