Mongoose 'Populate' on nested array returns null - node.js

I have recently started using the mongoose populate option for one of my API requests, which works fine for standard objects and schema, but I am struggling to make it work for a nested array.
My Schema looks like the following (this is the schema I am trying to retrieve with populate):
const FoodSchema = new Schema( {
name: {type: String, required: true},
price: {type: String, required: true},
category: {type: String, required: true},
...
})
I then have:
const OrderFoodSchema = new Schema(
{
food: {type: Schema.Types.ObjectId, required: true, ref: 'food'},
quantity: {...},
price: {...},
},
{ _id: false });
&&
const OrderSchema = new Schema( {
customer: {...},
venue: {type: Schema.Types.ObjectId, required: true, ref: 'venue'},
foods: [OrderFoodSchema]
})
My query to get the data is:
return Order.findOne({ _id: order_id, customer: user._id })
.populate({path:'venue',select:['name','address','contact']})
.orFail()
.then(function(order) {
res.json(order)
})
.catch(next);
The above populate works fine for the venue (probably because I am only populating one deep). But I cannot seem to get a working populate for the Food, it always returns null.
I have tried:
.populate('foods.food')
.populate({path:'foods.food'})
.populate({
path: 'foods',
populate: {
path: 'food',
}
})
How can I achieve this? Thanks.

Related

How to filter documents on mongodb based on reference populated subdocuments?

I am trying to grab documents based on populated subdocuments.
Here are my models
// User model
var UserSchema = new mongoose.Schema({
username: {type: String, required: true, trim: true},
firstName: {type: String, required: true, lowercase: true},
lastName: {type: String, required: true, lowercase: true},
phone: {type: String, required: false},
email: {type: String, required: true},
password: {type: String, required: true},
blogs: {type: mongoose.Schema.Types.ObjectId, ref: 'Blogs'}
}, {timestamps: true});
// Blog Model
var BlogSchema = new mongoose.Schema({
description: String,
tags: [String],
other: [Object],
}, {timestamps: true});
This is how I am grabbing documents
fetchAllByFilter: async function(req, res) {
try {
let result = await Users.find({}).populate('blog');
return res.status(200).send(result);
} catch (err) {
return res.status(200).send({error: err});
}
},
Now my main question is, how would I grab Users based on their Blogs referenced documents?
For example, Find Users with Blogs that has Blog.tags of "food", "cars", "movies" and/or Blog.other of [{...SomeObject}, {...SomeOtherObject}]
looking at mongo docs match an array, you could make a utility function somewhat like this...
async function findByTag(tag) {
const blogIds = await Blog.find({ tags: tag }).select("_id");
const users = await User.find({
blogs: { $in: blogIds.map((blog) => blog._id) }
}).populate("blog");
}

Mongoose – querying subdocuments referenced by ID

I have 2 schemas connected like this:
const Brand = new mongoose.Schema({
_id: { type: String, required: true },
name: {
type: String,
required: true,
},
products: [{
type: String,
ref: 'Product',
}],
});
const Product = new mongoose.Schema({
_id: { type: String, required: true },
name: {
type: String,
required: true,
},
type: {
type: String,
required: true,
},
});
I want to find brands that have certain types of products, so I wrote a query (not including async/await and promises in the code below for simplicity)
const docs = Brand.find({ 'products.type': 'my-type-here' })
.populate([
{
path: 'products',
},
])
.sort({ index: 1 })
.exec();
This gives me 0 results, yet I know that there are brand with the type of products. What am I doing wrong here? Is it connected with the fact, that products are only referenced by their ids when I invoke find method?

mongoose fetch posts by user and by his followings

How to fetch posts by user and all his followings posts (Mongodb, Mongoose, Nodejs)
User Schema
const userSchema = new mongoose.Schema({
firstName: { type: String, required: true, trim: true },
lastName: { type: String, required: true, trim: true },
});
userSchema.set('timestamps', true);
export default mongoose.model('user', userSchema);
Followers Schema
const followSchema = new mongoose.Schema({
follower: { type: mongoose.Schema.Types.ObjectId, ref: 'user' },
following: { type: mongoose.Schema.Types.ObjectId, ref: 'user' },
status: { type: Boolean, default: true }
});
followSchema.set('timestamps', true);
export default mongoose.model('follow', followSchema);
Posts Schema
const postSchema = new mongoose.Schema({
userId: { type: mongoose.Schema.Types.ObjectId, ref: 'user' },
contents: { type: String, trim: true },
photo: { type: String }
});
postSchema.set('timestamps', true);
export default mongoose.model('post', postSchema);
Thank you in advance! :)
Hy Alamghir its happy to see you here and sorry that you still did not get the answer what i can suggest you after seeing your schema that i think there is no need to create three collections you only need 2 schema first one
const userSchema = new mongoose.Schema({
firstName: { type: String, required: true, trim: true },
lastName: { type: String, required: true, trim: true },
followers:[]
});
userSchema.set('timestamps', true);
export default mongoose.model('user', userSchema);
Now in followers array just push the ids of users who followed this user Now it would be very easy for you to get posts done by these people like this let suppose you have user data in userData variable now you can do this
db.postSchema.find($or:[{userId:userData._id},{userId:$in:userData.followers}])
Sorry, got your question wrong.
There might be a better solution but what you should be able to do is this:
(this gets the posts of the people that are following your original user. If you did mean it the other way around, just switch :) )
// get the user's posts:
var theUserPosts = await postSchema.find({userId: userData._id}).exec();
// get the follower-IDs:
var theFollowersIDs = await followSchema.find({follower: userData._id, status: true}, 'following').exec();
// get the posts of those followers:
var followerPosts = await postSchema.find({userId: {$in: theFollowersIDs}}).exec();
Can you try this and tell us if this works for you?

Right way to store Schemas on Mongoose?

I started learning some NodeJS, and how to make a REST API from Academind on YouTube, and learned what a relational and non-relational database is, etc.
With MongoDB, writes are rather cheap, so I want to minimize the number of reads that I do. At the moment I am trying to see how I could make an API, that will be for an app that's similar to discord's, although it'll be for fun.
Is this the right way to make a Schema?
const mongoose = require('mongoose')
const userSchema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
name: { type: String, required: true, unique: true},
email: { type: String, required: true },
password: { type: String, required: true }, // TODO: Hashing, etc
guilds: [{
_id: mongoose.Schema.Types.ObjectId,
name: {type: String, required: true},
channels: [{
_id: mongoose.Schema.Types.ObjectId,
name: {type: String, required: true},
// Only the X most recent messages
messages: [{
_id: mongoose.Schema.Types.ObjectId,
message: {type: String, required: true},
user: {
_id: mongoose.Schema.Types.ObjectId,
name: {type: String, required: true}
}
}]
}],
// Only an X amount of users
users: [{
_id: mongoose.Schema.Types.ObjectId,
name: {type: String, required: true}
}]
}]
})
module.exports = mongoose.model('User', userSchema)
And then for the Guilds,
const mongoose = require('mongoose')
const guildSchema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
name: {type: String, required: true},
channels: [{
_id: mongoose.Schema.Types.ObjectId,
name: {type: String, required: true},
// Only an X amount of messages
messages: [{
_id: mongoose.Schema.Types.ObjectId,
message: {type: String, required: true},
user: {
_id: mongoose.Schema.Types.ObjectId,
name: {type: String, required: true}
}
}]
}],
// All the users
users: [{
_id: mongoose.Schema.Types.ObjectId,
name: {type: String, required: true}
}]
})
module.exports = mongoose.model('Guild', guildSchema)
Channel Schema
const mongoose = require('mongoose')
const channelSchema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
name: { type: String, required: true },
guild: {
_id: mongoose.Schema.Types.ObjectId,
name: { type: String, required: true },
channels: [{
_id: mongoose.Schema.Types.ObjectId,
name: { type: String, required: true }
}],
// The users of the guild, or just the channel?
// Could add a users object outisde of the guild object
users: [{
_id: mongoose.Schema.Types.ObjectId,
name: { type: String, required: true }
}]
}
})
module.exports = mongoose.model('Channel', channelSchema)
And finally for the messages
const mongoose = require('mongoose')
const messageSchema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
user: {
_id: mongoose.Schema.Types.ObjectId,
name: {type: String, required: true}
},
message: {type: String, required: true},
channel: {
guild: {
_id: mongoose.Schema.Types.ObjectId,
name: {type: String, required: true}
// Store more data for each message?
}
}
})
module.exports = mongoose.model('Message', messageSchema)
I am not sure if this is how a non-relational schema should look like. If it's not, how would I go about to store the data that I need?
Also, let's say that I POST a message on channel X on guild Y with the users A B and C, how would I go about to update all the entries, to add a message?
I've only used the User.find({_id: id}).exec().then().catch() so far, so I am not sure how to go about to update them.
Thanks in advance!
The messages collection should be on its own, do not embed it into any collection. This is not a good idea to embed data that will grow without limit.
The idea to store the last 5 messages into other collection looks painful to implement.
Embed denormalised data from all collections into the users collection seems like a problem when you will have to update guilds, channels, guilds users.
You may embed channels into guilds. Channels would not grow without a limit, should be a reasonable amount, less than 100 of channels per guild and probably it always used with a guild that they belong to. If not, consider not to embed channels into guilds.
The power of mongodb is to build the schema that reflects how your app is using data. I would recommend starting with normalized data. And when problems with creating, reading, updating, deleting data will occur then make appropriate changes in your mongoose schema to solve the problem. Premature optimization will only hurts in the long run.
As always an answer depends on details. Since I do not know all details I would recommend three part article by William Zola, Lead Technical Support Engineer at MongoDB. part 1 part 2 part 3

How to name a foreign key different from db name in mongoose

Is there a way in mongoose + Node.js/Express to define the relation between the foreign key field and what I refer to that field in the model is? My issue is that I have a mongo database where my foreign keys are all formatted like 'exampleId' instead of 'example'. I could just call out 'exampleId' directly but then it leads to weird things like when I populate 'exampleId' instead of 'example' (which is confusing because once populated, it is now the 'example' itself instead of its id).
Here is how I do it now and it works with my graphQL server, but only if my field in the database is 'course' while my database's field is 'courseId'
const CourseSchema = new Schema({
_id: { type: String },
sections: [{
type: Schema.Types.String,
ref: 'Section'
}],
});
const SectionType = new GraphQLObjectType({
name: 'SectionType',
fields: () => ({
id: { type: GraphQLID },
courseId: {
type: require('./course_type'),
resolve(parentValue) {
return Section.findById(parentValue)
.populate('course')
.then(section => section.course);
}
},
}),
});
I figured it out! With the newest version of mongoose, you actually can use virtual fields to accomplish what I wanted to do and this technique allows for flexibility in laying out your schema. Say that my MongoDB collections look like the following:
Courses { _id, sectionIds }
Lectures { _id, courseId }
I can use the following schema in mongoose and it will allow me to refer to course.lectures or lecture.course instead of the usual course.lectureIds or section.courseId:
const CourseSchema = new Schema({
_id: { type: String },
});
CourseSchema.virtual('sections', {
type: Schema.Types.String,
ref: 'Section',
localField: 'sectionIds',
foreignField: '_id',
justOne: false,
});
CourseSchema.statics.findSections = function(id) {
return this.findById(id)
.populate('sections')
.then(course => course.sections);
}
const SectionSchema = new Schema({
_id: { type: String },
});
SectionSchema.virtual('course', {
type: Schema.Types.String,
ref: 'Course',
localField: 'courseId',
foreignField: '_id',
justOne: true,
});
Actually MongoDB isn't a relational database. You can alter the field and its name whatever you like. Ex I Have an Owner(Meteor.users) table and Patient Table with this column
ownerid : {type: String, min: 1},
firstname: {type: String, min: 1},
lastname: {type: String, min: 1},
middlename: {type: String, min: 1, optional: true},
createdbyid: { type: String },
createdbyname: { type: String },
createdat: { type: Date, defaultValue: new Date() },
updatedbyid: { type: String, optional: true },
updatedbyname : { type: String, optional: true },
updatedat: { type: Date, defaultValue: new Date() },
I can easily stamp the value of my {Meteor.Users()._id} to ownerid of my designated patient by just processing them at meteor.methods. You don't have to worry about foreign keys mongo doesn't do relational databases you can customize your database whatever you like. I Hope this helps ;)
Mongoose Documentation posits that _id has to be used in refs and that[i]t is important to match the type of _id to the type of ref. , e.g.:
var personSchema = Schema({
_id : Number, //it is `Number`
name : String,
age : Number,
stories : [{ type: Schema.Types.ObjectId, ref: 'Story' }]
});
var storySchema = Schema({
_creator : { type: Number, ref: 'Person' },
title : String,
fans : [{ type: Number, ref: 'Person' }] // use `Number` to match
});
I also wonder if by 'database' you mean 'collection'.

Resources