Joining two mongoose collections with many to many relationship - node.js

I have two models which are Product & Category with many to many relationship.
Following are my models
This is the model for Product
//Product.js
const mongoose = require("mongoose");
const { Schema } = mongoose;
const { ObjectId } = Schema;
const product = {
name: {
type: String,
required: true,
trim: true,
minlength: [2, "Too short"],
maxlength: [32, "Too long"],
unique: true,
},
slug: {
type: String,
unique: true,
lowercase: true,
index: true,
},
category: [
{
type: ObjectId,
ref: "Category",
},
],
description: {
type: String,
maxlength: 200,
},
price: {
type: Number,
required: true,
trim: true,
maxlength: 32,
},
shipping: {
type: String,
enum: ["Yes", "No"],
},
color: [
{
type: String,
enum: ["Black", "Brown", "Silver", "White", "Blue"],
},
],
sold: {
type: Number,
default: 0,
},
quantity: {
type: Number,
},
images: [
{
public_id: String,
url: String,
},
],
};
const productSchema = new Schema(product, { timestamps: true });
const Product = mongoose.model("Product", productSchema);
module.exports = Product;
This is the model for Category
//Category.js
const mongoose = require("mongoose");
const { Schema } = mongoose;
const { ObjectId } = Schema;
const category = {
name: {
type: String,
required: true,
trim: true,
max: 32,
unique: true,
},
subCategories: [
{
type: Schema.Types.ObjectId,
ref: "Category",
},
],
parent: {
type: Schema.Types.ObjectId,
ref: "Category",
},
products: [
{
type: ObjectId,
ref: "Product",
},
],
slug: {
type: String,
required: "URL can't be empty",
unique: true,
},
};
const categorySchema = new Schema(category, { timestamps: true });
//Validate the slug to ensure proper url is given
// categorySchema.path("slug").validate((val) => {
// urlRegex =
// /(ftp|http|https):\/\/(\w+:{0,1}\w*#)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%#!\-/]))?/;
// return urlRegex.test(val);
// }, "Invalid URL.");
const autoPopulateChildren = function (next) {
this.populate("subCategories");
// this.populate("parent");
next();
};
categorySchema
.pre("findOne", autoPopulateChildren)
.pre("findById", autoPopulateChildren)
.pre("find", autoPopulateChildren);
const Category = mongoose.model("Category", categorySchema);
module.exports = Category;
So, product has a foreign keys of category in array of category. Whereas Category has foreign keys of product in array of products. How do you join them ?
I tried this & it doesn't work. It returns empty object
router.get("/products", [RequireSignIn], async (req, res) => {
try {
const products = await Product.find({}).aggregate({
$lookup: {
from: "Category",
localField: "category",
foreignField: "_id",
as: "ProductCategories",
},
});
return res.status(200).send(products);
} catch (error) {
return res.status(200).send(error);
}
});

Related

How to return documents in mongoose and express js which are associated with only logged in user?

Here i have two mongoose models orders and users which have one to many relationship.
user.model.js
import mongoose from "mongoose";
const userSchema = mongoose.Schema(
{
firstname: {
type: String,
required: [true, "Name is required"],
},
lastname: {
type: String,
required: [true, "LastName is required"],
},
email: {
type: String,
required: [true, "Email is required"],
unique: true,
},
password: {
type: String,
required: [true, "Password is required"],
},
isAdmin: { type: Boolean, default: false },
},
{
timestamps: true,
}
);
const User = mongoose.model("User", userSchema);
export default User;
order.model.js
import mongoose from "mongoose";
const orderSchema = mongoose.Schema(
{
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
},
customerId: { type: String },
paymentIntentId: { type: String },
products: [
{
id: { type: String },
name: { type: String },
category: { type: String },
price: { type: String },
size: { type: String },
color: { type: String },
thumbnail: { type: String },
qty: { type: Number },
},
],
// subTotal: { type: Number, required: true },
total: { type: Number, required: true },
shipping: { type: Object, required: true },
deliveryStatus: { type: String, default: "pending" },
paymentStatus: {
type: String,
required: true,
},
},
{
timestamps: true,
}
);
const Order = mongoose.model("Order", orderSchema);
export default Order;
I have some orders created by different users in my database. Now i am trying to get those specific orders associated with currently logged in user.
order.controller.js
export const getAllOrders = async (req, res) => {
const { _id } = req.user;
// console.log(typeof id);
try {
const orders = await Order.find({userId: _id});
console.log(orders);
res.status(200).json({ orders });
} catch (error) {
res.status(500).json({ msg: error.message });
}
};
I have tried this one but it always return an empty array.

Populate with type in mongoose

Hello I have a problem with populating. I would like to populate and have a type of populated list correctly. I have got error that
"Property 'name' does not exist on type 'ObjectId'."
Basically I would like to have a correct type of products.items that p.product is IProduct
populate:
const products = await order.populate<{ product: IProduct }>("items.product");
const pdfPath = path.join("data", pdfName);
const pdfStream = fs.createWriteStream(pdfPath);
const pdfDoc = new PDFDocument();
const writeStream = pdfDoc.pipe(pdfStream);
pdfDoc.fontSize(25).text(`Order ${orderId}`);
pdfDoc.fontSize(13).text(`User ${orderUser.name}`);
console.log("PRODUCTS", products);
products.items.forEach((p) => {
pdfDoc.fontSize(14).text(p.product.name);
});
schema:
const order = new Schema<IOrder>(
{
totalPrice: {
type: Number,
required: true,
},
userId: {
type: Schema.Types.ObjectId,
required: true,
},
items: [
{
_id: false,
product: {
type: Schema.Types.ObjectId,
ref: "Product",
required: true,
},
quantity: { type: Number, required: true },
},
],
},
{ collection: "orders", timestamps: true }
);

Nodejs mongoose get results from multiple collections in one query

I'm very new to Nodejs and MongoDB, I have 3 collections one for chapter,one for lecture and one for asset
every course have chapters, every chapter has array of lectures and every lecture have assets
so I want to get chapters by courseId which already done, and inside chapter to get its lectures and in every lecture to get its assets
Course:
const mongoose = require('mongoose');
var Double = require('#mongoosejs/double');
const courseSchema = new mongoose.Schema({
title: {
type: String,
required: true,
},
requirements: {
type: String,
},
code: {
type: String,
required: true,
},
coverPhoto: {
type: String,
required: false,
},
description: {
type: String
},
instructor:{
type:mongoose.Schema.Types.ObjectId,
ref:'User',
required:true
},
category:{
type:mongoose.Schema.Types.ObjectId,
ref:'Category',
required:true
},
learns: [{
type: String
}],
subCategory:{
type:mongoose.Schema.Types.ObjectId,
ref:'SubCategory',
required:true
},
isCoaching: {
type: Boolean,
default: false,
},
isFree: {
type: Boolean,
default: false,
},
price: {
type: Double,
default: 0,
},
rating: {
type: Double,
default: 0,
},
isPublished: {
type: Boolean,
default: false,
},
dateCreated: {
type:Date,
default:Date.now,
},
});
exports.Course = mongoose.model('Course', courseSchema);
exports.courseSchema = courseSchema;
Chapter:
const mongoose = require('mongoose');
const chapterSchema = new mongoose.Schema({
course:{
type:mongoose.Schema.Types.ObjectId,
ref:'Course',
required:true
},
title: {
type: String,
required: true,
},
sort_order: {
type: Number,
default: 1,
},
is_published: {
type: Boolean,
default: true,
},
});
exports.Chapter = mongoose.model('Chapter', chapterSchema);
exports.chapterSchema = chapterSchema;
Lecture:
const mongoose = require('mongoose');
const lectureSchema = new mongoose.Schema({
chapter:{
type:mongoose.Schema.Types.ObjectId,
ref:'Chapter',
required:true
},
title: {
type: String,
required: true,
},
sort_order: {
type: Number,
default: 1,
},
is_published: {
type: Boolean,
default: true,
},
});
exports.Lecture = mongoose.model('Lecture', lectureSchema);
exports.lectureSchema = lectureSchema;
Asset:
const mongoose = require('mongoose');
const assetSchema = new mongoose.Schema({
lecture:{
type:mongoose.Schema.Types.ObjectId,
ref:'Lecture',
required:true
},
title: {
type: String,
required:true
},
asset_type: {
type: String
},
description: {
type: String,
require:true
},
file_url: {
type: String,
require:true
},
page_number: {
type: Number,
default:1
},
time_estimation: {
type: String,
require:true
},
is_external: {
type: Boolean,
default: false,
},
is_published: {
type: Boolean,
default: true,
},
});
exports.Asset = mongoose.model('Asset', assetSchema);
exports.assetSchema = assetSchema;
Get Chapters of a course
router.get(`/`, async (req, res) => {
let course_filter = {};
if (req.query.course) {
course_filter = {course:req.query.course};
}
const chapterList = await Chapter.find(course_filter).populate('lecture').sort('sort_order');
if (!chapterList) {
res.status(500).json({ success: false });
}
res.send(chapterList);
});
using simple aggregation:
const chapterList = await Chapter.aggregate([{
$lookup: {
from: 'lectures',
localField: '_id',
foreignField: 'chapter',
as: 'lectures'
}}]);
You have to nest the populate in another populate:
router.get(`/`, async (req, res) => {
let course_filter = {};
if (req.query.course) {
course_filter = { course: req.query.course };
}
const chapterList = await Chapter.find(course_filter)
.populate({ path: 'lectures', populate: { path: 'assets' } })
.sort('sort_order');
if (!chapterList) {
res.status(500).json({ success: false });
}
res.send(chapterList);
});
You have to ensure that you have set a virtual for 'assets' prop in lectureSchema accordingly. I assume you have also done it for your 'Chapter' schema.
If not, you have do add the following:
virtual for Chapter schema:
chapterSchema.virtual('lectures', {
ref: Lecture.collection.collectionName,
localField: '_id',
foreignField: 'chapter'
});
virtual for Lecture schema:
lectureSchema.virtual('assets', {
ref: Asset.collection.collectionName,
localField: '_id',
foreignField: 'lecture'
});

Implementing addFields with Mongoose(MongoDB)

I am trying to calculate the average value of the ratings for a Product. Instead of calculating the average rating of the Product every time when we need the value. I calculate it every time someone rates it. I am expecting to see additional fields in the Document but for some reason console.log('stats: ', stats); is printing out an empty array and the fields are not added to the Document. Let me know what I am doing wrong.
const calculateAvgRating = async (id) => {
try {
const stats = await Product.aggregate([
{ $match: { _id: id } },
{
$addFields: {
avgRating: {
$avg: '$ratings.star',
},
nRatings: {
$size: '$ratings',
},
},
},
]);
console.log('stats: ', stats);
} catch (err) {
console.log(`calculateAvgRating error: ${err}`);
}
};
Schema
import mongoose from 'mongoose';
const { ObjectId } = mongoose.Schema;
const ParentSchema = new mongoose.Schema(
{
title: {
type: String,
trim: true,
required: true,
maxlength: 32,
text: true,
},
slug: {
type: String,
unique: true,
lowercase: true,
index: true,
},
price: {
type: Number,
required: true,
trim: true,
maxlength: 32,
},
quantity: Number,
ratings: [
{
star: Number,
postedBy: { type: ObjectId, ref: 'User' },
},
],
},
{
timestamps: true,
}
);
export default mongoose.models.Product ||
mongoose.model('Product', ParentSchema);

Populate() Mongoose is not returning joined data

Well, I´am trying to retrieve Name and Lastname from user who created the document but it´s not working, it still returning Mongo´s Id
This is my areas model
var mongo = require('mongoose'),
validator = require('mongoose-unique-validator'),
Schema = mongo.Schema
var model = new Schema({
NAME: { type: String, required: true, unique: true, max: 50, min: 3 },
STATUS: { type: String, default: 'active' },
ADDED_BY: { type: Schema.Types.ObjectId, ref: 'users' },
ADDED_DATE: { type: Date, default: Date.now }
}, {collection :'areas'})
model.plugin( validator, { message: 'The {PATH} is not valid or duplicated' } )
module.exports = mongo.model('Area', model )
This is the user model
var mongo = require('mongoose'),
validator = require('mongoose-unique-validator'),
Schema = mongo.Schema
var userSchema = new Schema({
PERSONAL_DATA: {
NAME: { type: String, required: [ true, 'The name is necessary' ], max: 50 },
LAST_NAME: { type: String, required: [ true, 'the lastname is necessary' ], max: 100 },
PHOTO: { type: String, max: 100 },
BIRTHDAY: { type: Date },
MARITIAL_STATUS: { type: Schema.Types.ObjectId, ref: 'maritial_statuses' },
GENDER: { type: String, max: 1 },
EMAIL: { type: String, required: true },
},
COMPANY_DATA: {
JOB: { type: Schema.Types.ObjectId, ref: 'jobs' },
AREA: { type: Schema.Types.ObjectId, ref: 'areas' },
ROLE: { type: Schema.Types.ObjectId, ref: 'roles' },
BOSS: { type: Schema.Types.ObjectId, ref: 'users' },
}
}, { collection: 'users' } )
model.plugin( validator, { message: 'The {PATH} is not valid or duplicated' } )
module.exports = mongo.model('User', userSchema )
And this is my areas route
var express = require('express'),
model = require('../../models/catalogs/areas'),
app = express()
app.get('/:from', (req, res) => {
var from = parseInt( req.params.from )
model.find()
.sort('NAME').populate({ path: 'users', select: 'NAME LAST_NAME'})
.limit(10).skip(from)
.exec((error, data) => {
if (error) {
return res.status(500).json({
success: false,
error
})
}
res.status(200).json({
success: true,
data
})
})
})
module.exports = app
The response is
{
"success": true,
"data": [
{
"STATUS": "active",
"_id": "5c547f4adadf433914f72c8c",
"NAME": "Contabilidad y Finanzas",
"ADDED_BY": "5c4f562deec6f4defeea759b",
"ADDED_DATE": "2019-02-01T17:18:02.680Z",
"__v": 0
},
{
"STATUS": "active",
"_id": "5c547f3edadf433914f72c8b",
"NAME": "Tecnologías",
"ADDED_BY": "5c4f562deec6f4defeea759b",
"ADDED_DATE": "2019-02-01T17:17:50.579Z",
"__v": 0
}
]
}
As you seen, ADDED_BY is a field joined to Users and I want to retrieve that information. I don´t know what is wrong with my code.

Resources