How to populate the nested objects in mongoose? - node.js

here's the booking schema
const bookingSchema = new mongoose.Schema({
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
required: true,
},
routeId:{
type: mongoose.Schema.Types.ObjectId,
ref: "Route",
required: true,
}
})
In this table the routeId(route schema) contains the Bus table(referenced).
const routeSchema = new mongoose.Schema({
location:{
type: mongoose.Schema.Types.ObjectId,
ref: 'Location',
required: true
},
duration: {
type: Number,
required: true
},
Bus:{
type: mongoose.Schema.Types.ObjectId,
ref:"Bus",
required: true
},
date: {
type:String,
required: true
},
},
{
timestamps: true,
});
and then finally the bus model
const busSchema = new mongoose.Schema(
{
busNumber: {
type: String,
unique: true,
required: true,
},
seats: {
type: Number,
},
},
{
timestamps: true,
}
);
Now I want to show only bus number and not whole bus data. this is the response I am getting. https://i.stack.imgur.com/S1qyZ.png
this is the query that I applied.
router.get("/bus/myBooking/one/:id", auth, async (req, res) => {
const _id = req.params.id;
const myBooking = await Booking.findById({ _id })
.populate({path: 'routeId', populate: 'Bus'})
.select(["-_id","-userId","-createdAt","-updatedAt","-__v","-passengers._id","-departureDetails._id","-arrivalDetails._id"]);
try {
return !myBooking
? res.status(404).send("No booking found")
: res.status(200).send(myBooking);
} catch (e) {
res.status(500).send();
}
});

This method works on some versions of mongoose, So you can try this:
await Booking.findById({ _id })
.populate({
path: 'routeId',
populate: {
path: 'Bus',
model: Bus,
select:"-_id busNumber"
}
})

Related

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'
});

Mongoose find problem - TypeError: Cannot read property 'find' of undefined

I'm having a issue with a mongoose find() query, which I cannot figure out. the error I receive is "TypeError: Cannot read property 'find' of undefined" which I suspect is an export/import problem. Any help would be greatly appreciated.
here is my scheme model file:
const mongoose = require('mongoose');
const RoleSchema = new mongoose.Schema({
pageGroup: {
type: String,
required: true,
},
level: {
type: String,
required: true,
}
})
const OfficeSchema = new mongoose.Schema({
officeId: {
type: mongoose.Schema.Types.ObjectId,
ref: "Office",
required: true,
},
roleId: {
type: [mongoose.Schema.Types.ObjectId],
required: false,
},
})
const InstanceSchema = new mongoose.Schema({
instanceId: {
type: mongoose.Schema.Types.ObjectId,
ref: "Instance",
required: true,
},
offices: {
type: [OfficeSchema],
required: false,
},
})
const UserSchema = new mongoose.Schema({
firstName: {
type: String,
required: true
},
lastName: {
type: String,
required: true
},
email: {
type: String,
required: false
},
password: {
type: String,
required: false
},
access: {
type: [InstanceSchema],
required: false,
},
permissions: {
type: [RoleSchema],
required: false,
},
activationToken: {
type: String,
required: false,
},
roleId: { // new
type: mongoose.Schema.Types.ObjectId,
// index: true,
ref: 'Role',
// default: null
},
employeeId: {
type: String,
required: false
},
instanceId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Instance',
required: true
},
officeId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Office',
required: true
},
},
{
toJSON: { virtuals: true },
toObject: { virtuals: true },
})
UserSchema.virtual('instances', {
ref: 'Instance',
localField: 'access.instanceId',
foreignField: '_id',
});
UserSchema.virtual('instances.offices', {
ref: 'Office',
localField: 'access.offices.officeId',
foreignField: '_id',
});
UserSchema.virtual('office', {
ref: 'Office',
localField: 'officeId',
foreignField: '_id',
justOne: true,
});
UserSchema.virtual('name').get(function() {
return this.firstName + " " + this.lastName
});
const User = mongoose.model('User', UserSchema);
module.exports = { User }
here is my function in my controller file:
const { User } = require('./user.model');
async getEmployees(){
const employees = await User.find({
instanceId: this._id,
}, '-password -activationToken -__v -activated')
.populate('office')
.sort([['firstName', 1]])
.exec()
return employees
},
The error points to User being undefined, which can happen when your project has cyclic dependencies (where file A.js depends on file B.js, which in turn depends on file A.js again, either directly or indirectly through another file).
A quick fix is to delay loading the User model until the moment it's actually needed, by moving the require() into getEmployees():
async getEmployees(){
const { User } = require('./user.model');
const employees = await User.find({
instanceId: this._id,
}, '-password -activationToken -__v -activated')
.populate('office')
.sort([['firstName', 1]])
.exec()
return employees
}
But ideally, you should get rid of the cyclic dependency altogether.
I had the same problem on my project. You can fix it by replacing
const { User } = require('./user.model');
of your controller by :
const User = require('./user.model');
Just by removing the brackets made it for me. So I guess you should use destructuring.

populate subdocument in mongoose

I hope someone can help me with this issue. I am trying to populate a subSchema but I have no luck.
I have a replySchema, commentSchema and a UserSchema.
replySchema is subDocument to commentSchema and I want to populate both of them with user's details from userSchema
My issue is that with the code below it does not populate my replySchema:
REPLY-SCHEMA and COMMENT-SCHEMA
const replySchema = new mongoose.Schema(
{
replyComment: {
type: String,
required: [true, 'Reply comment cannot be empty'],
},
createdAt: {
type: Date,
default: Date.now(),
},
user: {
type: mongoose.Schema.ObjectId,
ref: 'User',
required: [true, 'Comment must belong to a user'],
},
},
{
timestamps: true,
}
);
const commentSchema = new mongoose.Schema(
{
comment: {
type: String,
required: [true, 'Comment cannot be empty'],
},
createdAt: {
type: Date,
default: Date.now(),
},
// createdAt: Number,
// updatedAt: Number,
post: {
type: mongoose.Schema.ObjectId,
ref: 'Post',
required: [true, 'Comment must belong to a Post'],
},
user: {
type: mongoose.Schema.ObjectId,
ref: 'User',
required: [true, 'Comment must belong to a user'],
},
replies: [replySchema], //replySchema sub-document - is this the right way?
},
{
// timestamps: { currentTime: () => Math.floor(Date.now() / 1000) },
toJSON: { virtuals: true },
toObject: { virtuals: true },
}
);
replySchema.pre(/^find/, function (next) {
this.populate({
path: 'user',
select: 'name role avatar',
});
next();
});
commentSchema.pre(/^find/, function (next) {
this.populate({
path: 'user',
select: 'name role avatar',
});
next();
});
const Comment = mongoose.model('Comment', commentSchema);
module.exports = Comment;
USER-SCHEMA
const userSchema = new mongoose.Schema(
{
name: {
type: String,
trim: true,
required: [true, 'Please insert your name'],
},
avatar: {
type: Object,
},
role: {
type: String,
enum: ['user', 'admin'],
default: 'user',
},
},
{
toJSON: { virtuals: true },
toObject: { virtuals: true },
}
);
const User = mongoose.model('User', userSchema);
module.exports = User;
Thank you very much for your time!
To populate user's data in replies:
this.populate([
'user', // To populate commentSchema's user field
{
path: 'replies',
populate: 'user'
} // To populate replySchema's user field
]);
Edit:
To populate specific fields:
this.populate([
{
path: 'user',
select: 'name role avatar'
},
{
path: 'replies',
populate: {
path: 'user',
select: 'name role avatar'
}
}
]);

Mongo populate not working and not retrieve data model

I have a problem in populate method in mongodb it can't retrieve data from model. Can anyone help me solve that problem?
This is the code
router.get('/', auth, async (req, res) => {
try {
const user = req.user._id;
const wishlist = await Wishlist.find({ user, isLiked: true })
.populate({
path: 'Products',
select: 'title',
})
.sort('-updated');
res.status(200).json({
wishlist,
});
} catch (error) {
res.status(400).json({
error: 'Your request could not be processed. Please try again.',
});
}
});
When I navigate to http://localhost:3000/wishlist/, this is the response I get:
{
"wishlist": [
{
"product": "60cb5eb82cc7091ae2e31c88",
"user": "60cb6c46291247466fe08f92",
"isLiked": true,
"_id": "60d1a656567e08bf89571209",
"updated": "2021-06-22T10:09:25.295Z",
"created": "2021-06-22T08:59:02.434Z",
"__v": 0
}
]
}
The model of products
const mongoose = require('mongoose');
const { Schema } = mongoose;
const ProductSchema = mongoose.Schema({
title: {
type: String,
required: true,
},
description: {
type: String,
required: true,
},
category:{
type: Schema.Types.ObjectId,
ref: 'Categories',
default: null
},
photo: { type: String, required: true },
createdAt: {
type: Date,
default: new Date(),
},
updateAt: Date,
price: {
type: String,
required: true,
},
quantity: {
type: Number
}
,
isActive: {
type: Boolean,
default: true
},
user: {
type: Schema.Types.ObjectId,
ref: "User",
}
});
module.exports = mongoose.model('Product', ProductSchema);
The model for wishlist:
const Mongoose = require('mongoose');
const { Schema } = Mongoose;
// Wishlist Schema
const WishlistSchema = new Schema({
product: {
type: Schema.Types.ObjectId,
ref: 'Product',
default: null,
},
user: {
type: Schema.Types.ObjectId,
ref: 'User',
default: null,
},
isLiked: {
type: Boolean,
},
updated: {
type: Date,
default: Date.now,
},
created: {
type: Date,
default: Date.now,
},
});
module.exports = Mongoose.model('Wishlist', WishlistSchema);
Can anyone help me please to find the solution for that problem?

Mongoose Virtual field with async getter

I have a item model where it a virtual field to refer stock badges.
'use strict';
const mongoose = require('mongoose');
const mongooseHidden = require('mongoose-hidden')();
const Badge = mongoose.model('Badge');
const validateProperty = function(property) {
return (property.length);
};
const Schema = mongoose.Schema;
const ItemSchema = new Schema({
itemCode: {
type: Number,
index: {
unique: true,
sparse: true // For this to work on a previously indexed field, the index must be dropped & the application restarted.
},
required: true
},
itemName: {
type: String,
uppercase: true,
trim: true
},
barcode: {
type: String,
trim: true
},
category: {
type: Schema.Types.ObjectId,
ref: 'Category'
},
subCategory: {
type: Schema.Types.ObjectId,
ref: 'SubCategory'
},
updated: {
type: Date
},
created: {
type: Date,
default: Date.now
},
status: {
type: String,
enum: [
'active', 'inactive', 'removed'
],
default: 'active'
}
}, {id: false});
ItemSchema.virtual('badges').get(function() {
return this.getAvailableBadges();
});
ItemSchema.methods.getAvailableBadges = function() {
Badge.find({
item: this._id
}, (err, badges) => {
if (badges) {
return badges;
} else {
return [];
}
});
};
ItemSchema.set('toJSON', {virtuals: true});
ItemSchema.set('toObject', {virtuals: true});
ItemSchema.plugin(mongooseHidden, {
hidden: {
_id: false,
__v: true
}
});
mongoose.model('Item', ItemSchema);
And batch model as below
'use strict';
const mongoose = require('mongoose');
const mongooseHidden = require('mongoose-hidden')();
const validateProperty = function(property) {
return (property.length);
};
const Schema = mongoose.Schema;
const BadgeSchema = new Schema({
item: {
type: Schema.Types.ObjectId,
ref: 'Item'
},
qty: {
type: Number,
validate: [validateProperty, 'Please enter Quantity !']
},
purchasingPrice: {
type: Number,
validate: [validateProperty, 'Please enter purchasingPrice !']
},
sellingPrice: {
type: Number,
validate: [validateProperty, 'Please enter sellingPrice !']
},
updated: {
type: Date
},
created: {
type: Date,
default: Date.now
},
status: {
type: String,
enum: [
'active', 'inactive', 'removed'
],
default: 'active'
}
});
BadgeSchema.plugin(mongooseHidden, {
hidden: {
_id: false,
__v: true
}
});
mongoose.model('Badge', BadgeSchema);
Item's badge virtual field doesn't got populated.
How are we going to work with async getter method
I have put some console log statements and found that getAvailableBadges is getting data.
I need to send json object with virtual field values via express. How to I do it?
What I did was create an virtual property
ItemSchema.virtual('badges', {
ref: 'Badge',
localField: '_id',
foreignField: 'item'
});
And populate it with
{
path: 'badges',
select: [
'qty', 'purchasingPrice', 'sellingPrice'
],
options: {
sort: {
'created': -1
}
}
}
Well, the operations are asynchronous so you have to wait for the callback to fire.
You can only return the values by passing it in the callback (or you can set the values of the current object prior to calling the callback).
I think it would be something like this:
ItemSchema.virtual('badges').get(function (callback) {
Badge.find({ item: this._id }, callback);
};
Then you would use it like
item.badges(function (err, badges) {
// do something with badges
});

Resources