MongoDB Mongoose schema design - node.js

I have a schema design question. I have a UserSchema and a PostSchema.
var User = new Schema({
name: String
});
var Post = new Schema({
user: { type: Schema.Types.ObjectId }
});
Also, user is able to follow other users. Post can be liked by other users.
I would like to query User's followers and User's following, with mongoose features such as limit, skip, sort, etc. I also want to query Post that a user likes.
Basically, my only attempt of solving this is to keep double reference in each schema. The schemas become
var User = new Schema({
name: String,
followers: [{ type: Schema.Types.ObjectId, ref: "User" }],
following: [{ type: Schema.Types.ObjectId, ref: "User" }]
});
var Post = new Schema({
creator: { type: Schema.Types.ObjectId, ref: "User" },
userLikes: [{ type: Schema.Types.ObjectId, ref: "User" }]
});
so, the code that will be used to query
// Find posts that I create
Post.find({creator: myId}, function(err, post) { ... });
// Find posts that I like
Post.find({userLikes: myId}, function(err, post) { ... });
// Find users that I follow
User.find({followers: myId}, function(err, user) { ... });
// Find users that follow me
User.find({following: myId}, function(err, user) { ... });
Is there a way other than doing double reference like this that seems error prone?

Actally, you don't need the double reference. Let's assume you keep the following reference.
var User = new Schema({
name: String,
following: [{ type: Schema.Types.ObjectId, ref: "User" }]
});
You can use .populate() to get the users you're following:
EDIT: added skip/limit options to show example for pagination
User.findById(myId).populate({ path:'following', options: { skip: 20, limit: 10 } }).exec(function(err, user) {
if (err) {
// handle err
}
if (user) {
// user.following[] <-- contains a populated array of users you're following
}
});
And, as you've already mentioned ...
User.find({following: myId}).exec(function(err, users) { ... });
... retrieves the users that are following you.

Related

nodejs/express mongoose .populate() on schema with refs to itself

I have this schema for a User:
const UserSchema = new Schema({
...
friends: [{
user: {
type: Schema.Types.ObjectId,
ref: 'users'
}
}],
sentRequests: [{
user: {
type: Schema.Types.ObjectId,
ref: 'users'
}
}],
recievedRequests: [{
user: {
type: Schema.Types.ObjectId,
ref: 'users'
}
}]
}
Then when trying to show all the recievedRequests to a page I use this code:
router.get('/friendRequests', (req, res) => {
User.findOne({
_id: req.user.id
})
.populate('recievedRequests.user')
.then(curUser => {
res.render('friends/friendRequests', {
curUser: curUser
});
})
});
where User is the model and req.user.id is the id of the currently logged in user. However, in the webpage whenever I reference curUser.recievedRequests.user it simply returns the id of the user and not the actual user object. I am assuming it has to do with the nested schema reference since I can't see anything else that would cause this issue. Does anyone know of a way to resolve this?
Thanks.

Mongoose Populate not working with Array of ObjectIds

Here is my schema:
/** Schemas */
var profile = Schema({
EmailAddress: String,
FirstName: String,
LastName: String,
BusinessName: String
});
var convSchema = Schema({
name: String,
users: [{
type: Schema.Types.ObjectId,
ref: 'Profiles'
}],
conversationType: {
type: String,
enum: ['single', 'group'],
default: 'single'
},
created: {
type: Date,
default: Date.now
},
lastUpdated: {
type: Date,
default: Date.now
}
});
/** Models */
db.Profiles = mongoose.model('Profiles', profile);
db.Conversations = mongoose.model('ChatConversations', convSchema);
module.exports = db;
Then I try to populate Users using following code (http://mongoosejs.com/docs/populate.html):
db.Conversations.find(query).populate('users').exec(function (err, records) {
console.log(records);
});
This is returning records but users array as a blank array [].
I also tried the other way around (http://mongoosejs.com/docs/api.html#model_Model.populate):
db.Conversations.find(query, function (err, records) {
db.Conversations.populate(records, {path: "users", select: "BusinessName"}, function (err, records) {
console.log(records);
});
});
Results are same. When I checked references into profile collection records are there.
Any idea what wrong here?
I got it working by renaming model (the 3rd arguement):
mongoose.model( "Profiles", profile, "Profiles" );
The issue was Mongoose was searching for profiles collection but its there as Profiles in database. So I renamed it to Profiles to match the exact name.
Phewww! Thanks to me.

Query Mongoose Schema. nested array of comments

I have venues, which each have a comments section. Each comment is a Mongoose Comment schema. Each comment has a creator property, which is a User schema. I'm trying to find all comments a specific user has posted. How can I do this?
var VenueSchema = new mongoose.Schema({
comments: [{
type : mongoose.Schema.Types.ObjectId,
ref: 'Comment',
default: []
}]
},
{minimize: false});
var CommentSchema = new mongoose.Schema({
creator: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}
}, {minimize: false});
var UserSchema = new mongoose.Schema({
token: String,
venues: [{ //in case we want users to save their favorite venues
type : mongoose.Schema.Types.ObjectId,
ref: 'Venue'
}]
});
I have tried
Venue.find({
"comments.creator": "55f1fa1263877ed0067b78c0"
}, function(err, docs) {
console.log(docs);
res.send(docs);
})
but it returns an empty array. The "55f1fa1263877ed0067b78c0" is a sample creator _id. Thanks in advance!
you cannot search creater by its id inside Venue collection becouse it collects only Comment ID. So in order to search creater by its id you need to change like below:
var VenueSchema = new mongoose.Schema({
comments: [CommentSchema]
},
{minimize: false});
var CommentSchema = new mongoose.Schema({
creator: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}
}, {minimize: false});
var UserSchema = new mongoose.Schema({
token: String,
venues: [{ //in case we want users to save their favorite venues
type : mongoose.Schema.Types.ObjectId,
ref: 'Venue'
}]
});
As venueSchema is storing only ref to comments (which would be comments _id), you will not be able to query comment using venue model. Either you have embed comment document into comments array of venue schema.
Or
Just query the comment collection using comment model as below
Comment.find({
"creator": "55f1fa1263877ed0067b78c0"
}, function(err, docs) {
console.log(docs);
res.send(docs);
})

What is the best practice to insert new entry with nodejs and mongoose

I have two models:
This is the first:
var CommentSchema = new Schema({
title: {
type: String
},
owner: {
type: Schema.ObjectId,
ref: 'User'
}
});
This is the second:
var UserSchema = new Schema({
name: {
type: String
},
comments: [{
type: Schema.ObjectId,
ref: 'Comment'
}]
});
There are really a lot of comments. So, i add the id of each comment in the array of comment of owner. The goal is to create a simple function like "list my comments".
The comments are verry actually, i use this code to add new comment:
var comment = ...
comment.title = "title";
comment.owner = req.user
comment.save(function(err) {
if (err)
...
else {
User.findOne({ _id: req.user._id }, function (err, tmpUser){
tmpUser.comments.push(comment);
tmpUser.save(function(err) {
if (err)
...
else {
//END
}
});
});
}
}
How to optimise this code ? Is it possible to optimise directly the model ? Thank you !
Why not simply query the Comments collection? If you're worried about the performance when there are a lot of comments, you can (and should) index the owner field.
db.comments.ensureIndex( { owner: 1 } )
And then using mongoose:
Comment.find({ owner: req.user._id }, function(err, comments) {
// do something with comments
});

Mongoose - Better solution with appending additional information

I have two Schemas:
var ProgramSchema = new Schema({
active: Boolean,
name: String,
...
});
var UserSchema = new Schema({
username: String,
email: { type: String, lowercase: true },
...
partnerships: [{
program: { type: Schema.Types.ObjectId, ref: 'Program' },
status: { type: Number, default: 0 },
log: [{
status: { type: Number },
time: { type: Date, default: Date.now() },
comment: { type: String },
user: { type: Schema.Types.ObjectId, ref: 'User' }
}]
}]
});
Now I want to get all Program docs, but also append 'status' to each doc, to return if the program is already in a partnership with the logged in user.
My solution looks like this:
Program.find({active: true}, 'name owner image user.payments', function (err, p) {
if(err) { return handleError(res, err); }
})
.sort({_id: -1})
.exec(function(err, programs){
if(err) { return handleError(res, err); }
programs = _.map(programs, function(program){
var partner = _.find(req.user.partnerships, { program: program._id });
var status = 0;
if(partner){
status = partner.status;
}
program['partnership'] = status;
return program;
});
res.json(200, programs);
});
The req.user object contains all information about the logged in user, including the partnerships array.
To get this solution to work, I have to append
partnership: Schema.Types.Mixed
to the ProgramSchema.
This looks a bit messy and thats why I am asking for help. What do you think?
When you want to freely modify the result of a Mongoose query, add lean() to the query chain so that the docs (programs in this case) are plain JavaScript objects instead of Mongoose doc instances.
Program.find({active: true}, 'name owner image user.payments')
.lean() // <= Here
.sort({_id: -1})
.exec(function(err, programs){ ...
Then you can remove partnership from your schema definition. Your query will also execute faster.

Resources