Query status of inner array of in MongoDB using mongoose - node.js

I have a User schema with followers array like below,
const userSchema = new mongoose.Schema({
firstname: {
type: String,
required: true,
},
lastname: {
type: String,
required: true,
},
followers: [{ type: ObjectId, ref: "User" }],
following: [{ type: ObjectId, ref: "User" }],
});
When i query for list of Users i need two extra fields isFollowing and isFollower
(isFollowing exists in my following array, isFollower exists in followers array)

Looks like you need to populate the followers and following arrays:
const users = await User.findOne({ ... })
.populate('followers')
.populate('following');

Related

mongoose populate doesn't populate

I have a list of users, each user have a list of exercises. I want to aggregate the basic user's info and their exercises to be displayed.
I did the following:
const userSchema = new mongoose.Schema({
name: {
type: String,
required: [true, 'Invalid username'],
unique: [true, 'This user already exists'],
},
exercises: {
type: [mongoose.Schema.Types.ObjectId],
ref: 'Exercise'
}
})
User = mongoose.model('User', userSchema);
And
const exerciseSchema = new mongoose.Schema({
user: {type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true},
description: {type:String, required: true},
duration: {type:Number, required: true},
date: {type:Date, required: true},
});
Exercise = mongoose.model('Exercise', exerciseSchema);
However, the aggregation part displays only the user's info with an empty array of exercises:
User.
find().
populate({
path: 'exercises',
model: 'Exercise'
}).
exec().
then(docs => res.json(docs).status(200)).
catch(err => res.json(err).status(500))
})
Gives:
[{
"exercises":[],
"_id":"6047b61bc7a4f702f477085b",
"name":"John Smith",
"__v":0}
]
Use the following aggregate query to get users and is exercise data.
User.aggregate([{
"$lookup":{
"localField":"exercises",
"foreignField":"_id",
"from":"Exercise",
"as" :"userExercises"
}
}])
You will get each user with its exercise data.

Mongoose 'Populate' on nested array returns null

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.

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 remove left-over ObjectId?

The problem is:
I have a collection of photos schema and likes schema, and inside photos there is an array of like ObjectIds called likeArray which is used to populate data from likes colletion.
But when i delete a like from likes collection, the ObjectId of that like in the likeArray still exists.
I tried to find the index of like._id in the likeArray and use likeArray.splice(index, 1) but couldn't work.
Can someone know the solution?
Here's the photo schema:
var Photo = mongoose.model('Photo', new mongoose.Schema({
photo_url: String,
photo_description: String,
user:{
id: {
type: mongoose.Schema.Types.ObjectId,
ref: 'user'
},
username: String,
profile_photo: String
},
comments: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'comment'
}
],
likes: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'like'
}
],
created_at: {type: Date, default: Date.now}
}));
Here's the Like schema:
var Like = mongoose.model('Like', new mongoose.Schema({
user_id: {
type: mongoose.Schema.Types.ObjectId,
ref: 'user'
},
photo_id: {
type: mongoose.Schema.Types.ObjectId,
ref: 'photo'
},
created_at: {type: Date, default: Date.now}
}));
Instead of splice you can use $pull operator. Here's how it'd look in mongo shell.
db.likes.remove({_id: like_oid});
db.photos.update({likes: like_oid}, {$pull: {likes: like_oid}}, { multi: true });

Why does this array-field query fail in Mongoose but not Mongo shell?

This is my User schema in Mongoose.
var userSchema = mongoose.Schema({
username: {
type: String,
unique: true,
required: true
},
password: {
type: String,
required: true
},
mobile: {
type: Number,
required: true
},
accounts: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Account'
}]
});
var User = mongoose.model('User', userSchema);
In mongo shell, this finds my existing user document:
db.users.find({ accounts:"57d03b30edfd30e806fb20c9"})
But with Mongoose, this finds the empty set:
User.find({ accounts: "57d03b30edfd30e806fb20c9" })
.exec(function (err, users) {
*** users == [] ***
});
User.find({ accounts: mongoose.Types.ObjectId("57d03b30edfd30e806fb20c9") })
or alternatively...
var ObjectId = mongoose.Types.ObjectId;
User.find({ accounts: ObjectId("57d03b30edfd30e806fb20c9") })
As #vdj4y mentioned, Mongoose is interpreting your query as a String instead of as an ObjectId (which I imagine it is?)
actually, if the schema matches, you should just treat ObjectId as a string
accounts: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Account'
}]
then query it like it is a string
User.find({ accounts: "57d03b30edfd30e806fb20c9" })
.exec(function (err, users) {
*** users == [] ***
});
but you need to delete the previous entry, since it is currently a "mixed" type. I am guessing mixed is really just a string, so you could try to use string type like below. well, the fact that you could query a string in mongoshell and find a result
accounts: [{
type: mongoose.Schema.Types.String,
ref: 'Account'
}]

Resources