MongoDB - Mongoose: document structure/architecture - javascript

I have a relatively simple question, however, I keep getting stuck, over and over again. I think this is the fourth time I have tried to re-structure my documents to try and get the desired result.
I want to create a database in mongoose with the following structure:
A User has many groups, posts and comments. The groups, posts and comments all belong to a User. A post can belong to many groups, or none. And a comment belongs to a certain post.
Here is what I have so far:
UserSchema
var userSchema = mongoose.Schema({
local : {
email : String,
password : String,
username : String,
created: {type:Date, default: Date.now}
}
});
PostSchema
var PostSchema = new Schema({
url: String,
highlighted: String,
comment: String,
image: String,
timeStamp: String,
description: String,
title: String,
created: {type:Date, default: Date.now},
private: Boolean,
favorite: Boolean,
readlater: Boolean,
postedBy: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
comments: [{
text: String,
postedBy: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}
}],
groups: [{
name: String,
postedBy: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}
}]
});
groupSchema
var GroupSchema = Schema({
name : String,
created: {type:Date, default: Date.now},
postedBy: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}
});
Although I may be wrong on this assumption, I think I have the post to user and comment to post relationship just the way it should be. Please correct me if I am wrong. The main problem I am having right now is with my group to post relationship. Currently, there is no relationship at all. As far as I can, it is almost as if the group belongs to the post, instead of the other way around. The only other way I can think to structure the group would be like so:
var GroupSchema = Schema({
name : String,
created: {type:Date, default: Date.now},
postedBy: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
posts: [{
title: String,
Url: String,
comments: [{
text: String
}]
etc....
}]
});
The only problem I can see in the structure above is that the post will be required to be part of a group, which I want to be optional. So as far as I can tell, this will not work either.
If there is any advice you can provide in regards to how this should be structured, it would help me greatly.
Thank you in advance.

Your best bet is to embed your groups and comment schemas inside of your post document. For example,
var GroupSchema = Schema({
name: String,
created: { type:Date, default: Date.now },
});
var CommentSchema = Schema({
text: String,
postedBy: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}
});
var PostSchema = new Schema({
url: String,
...,
postedBy: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
comments: [CommentSchema]
groups: [GroupSchema]
});
Now, if you are looking for posts with a particular group, you can build a query as follows. You can build more complicated queries than this however.
Post.elemMatch("groups", {name: {$in: ["group1", "group2"]}}).then(...)

Related

How to implement a deep populate using the Mongoose, to properly output a M:N database relation?

I am trying to create a basic social media website, with post having different comments, likes, comments also having likes. If the logged in user is the one that made the comment or post, he could be able to delete the post.
So I have to use the deep populate method of mongoose, but the issue I am having is, when the content of the comment is showing, then the name of the user that made that comment is missing.
Post Schema is as follows
content: {
type: String,
required: true
},
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
// include the array of ids of all comments in this post schema itself
comments: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Comment'
}
],
likes: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Like'
}
]
},{
timestamps: true
});
Like Schema is as follows -
const likeSchema = new mongoose.Schema({
user: {
type: mongoose.Schema.ObjectId
},
// this defines the object id of the liked object
likeable: {
type: mongoose.Schema.ObjectId,
require: true,
refPath: 'onModel'
},
// this field is used for defining the type of the liked object since this is a dynamic reference
onModel: {
type: String,
required: true,
enum: ['Post', 'Comment']
}
}, {
timestamps: true
});
User Schema is as follows -
const userSchema = new mongoose.Schema({
email: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true
},
name: {
type: String,
required: true
},
avatar: {
type: String
}
}, {
timestamps: true
});
Comment Schema is as follows -
const commentSchema = new mongoose.Schema({
content: {
type: String,
required: true
},
// comment belongs to a user
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
post: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Post'
},
likes: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Like'
}
]
},{
timestamps: true
});
And this is the populate function, on the front end I am trying to display all possible posts with all possible likes and comments, with all the comments also having likes. And obviously the name of the user that made the comment -
let posts = await Post.find({})
.sort('-createdAt')
.populate('user')
.populate({
path: 'comments',
populate: {
path: 'user'
},
populate: {
path: 'likes'
}
}).populate('comments')
.populate('likes');
But in the front end, I am not able to display the User Name that made a particular comment.
Please tell the error.

Default values not adding to mongoose Model

So this is an example of my schema I have for a user.
id: String,
email: String,
slug: {
type: Object,
phrase: {type: String, default: null},
},
When I want to define a new user and save that user, I would do the following;
const newUser = new User({
id: 123,
username: "CoolUser",
email: "BillGates#google.com"
});
newUser.save();
But this does not save the "slug" object, It was my understanding, that since I a default value for it, it would auto populate with that default value. What can I do to make it auto generate without having to define the whole schema again when saving a user?
You should add default for slug property, and not for his sub-property. Try changing your schema like this:
slug: {
type: {
phrase: { type: String },
},
default: {
phrase: null
},
},
try this:
const subschema = new Schema({
phrase: {type: String, default: null},
}, { _id: false });
and in your original schema:
id: String,
email: String,
slug: {
type: subschema,
default: () => ({})
},
this should do the trick.
const User = new Schema({
id: String,
about: {
bio: String,
location: String,
website: String,
discord: String,
twitter: String,
default: {
bio: "This is a default bio.",
location: "",
website: "",
discord: "",
twitter: "",
}
}
})
Ok here is the example, I removed some stuff from it just for ease, Basically as you can see I want bio to default to: This is a default bio.
But instead, I get the following error:
throw new TypeError(`Invalid schema configuration: \`${name}\` is not ` +
^
TypeError: Invalid schema configuration: `This is a default bio.` is not a valid type at path `about.default.bio`. See bit ly / mongoose-schematypes for a list of valid schema types.

Creating a MongoDB schema with references to other schemas

I am having some difficulties understanding how construct my schemas and am hoping to get some help.
In short I have three schemas Company, User and Verification. I start by creating some companies and then create and add users to the companies.
I then log in to a webpage and use a form to add rows to a table (konto, beskrivning, projekt...). Then I save the verification to a MongoDB.
First question is: Would the current schemas work for my scenario? or do I need to change something. Have I entered redundant information?
Second question: In the VerificationSchema I would like to have an incrementing value (verificationNumber) for each company that starts by 1 then increases for every added verification. The counter should be separate for each company but shared with all users in the same company.
Verification.js
const VerificationSchema = mongoose.Schema({
verificationNumber: Number,
user :{type: mongoose.Schema.Types.ObjectId, ref: 'User'},
company: {type: mongoose.Schema.Types.ObjectId, ref: 'Company'},
rows: [{
konto: Number,
beskrivning: String,
projekt: String,
debet: Number,
kredit: Number
}]
});
Company.js
const CompanySchema = mongoose.Schema({
companyName: String,
users: [{type: mongoose.Schema.Types.ObjectId, ref: 'User'}],
verifications: [{type: mongoose.Schema.Types.ObjectId, ref: 'Verification'}]
});
User.js
const UserSchema = mongoose.Schema({
userName: String,
company: {type: mongoose.Schema.Types.ObjectId, ref: 'Company'],
verifications: [{type: mongoose.Schema.Types.ObjectId, ref: 'Verification'}]
});

Returning specific fields with mongoose

I'm trying to accomplish something really easy but still manage to fail.
What I am trying to do is when I get a get request on my server I want to return all documents BUT just the specific fields populated.
My schema goes as follows
var clientSchema = new Schema({
name:{
type: String,
required: true
},
phone:{
type: String,
required: true
},
email:{
type: String,
required: true
},
address: {
type: String,
required: false
}
});
var orderDetailsSchema = new Schema({
//isn't added to frontend
confirmed:{
type: Boolean,
required: true,
default: false
},
service:{
type: String,
required: true
},
delivery:{
type: String,
required: false
},
payment:{
type: String,
required: false
},
status:{
type: String,
required: true,
default: "new order"
},
});
var orderSchema = new Schema({
reference:{
type: String,
required: true
},
orderdetails: orderDetailsSchema,
client: clientSchema,
wheelspec: [wheelSchema],
invoice:{
type: Schema.Types.ObjectId,
ref: 'Invoice'
}
});
What I want is to return only client.phone and client.email plus orderdetails.status but still retain reference field if possible
I have tried using lean() and populate() but had no luck with them. Is there anything utterly simple I am missing? Or what I am trying to achieve is not that easy?
Thanks!
You can specify the fields to return like this:
Order.findOne({'_id' : id})
.select('client.phone client.email orderdetails.status reference')
.exec(function(err, order) {
//
});
Alternative syntax
Order.findOne({'_id' : id})
.select('client.phone client.email orderdetails.status reference')
.exec(function(err, order) {
//
});
I've made a number of assumptions here, but you should be able to see the idea.
Simply do like this :-
Order is model name which is registered in mongoose.
Order.findById(id) // set id you have to get
. populate('client')
.select('client.phone client.email orderdetails.status reference')
.exec(function(err, order) {
//
});
You can use projection.
await Order.findById(diaryId, {phone: 1, email: 1, status: 1})
If phone:1 is set to 1, it is included, if phone:0, then it's excluded.

How To Create Mongoose Schema with Array of Object IDs?

I have defined a mongoose user schema:
var userSchema = mongoose.Schema({
email: { type: String, required: true, unique: true},
password: { type: String, required: true},
name: {
first: { type: String, required: true, trim: true},
last: { type: String, required: true, trim: true}
},
phone: Number,
lists: [listSchema],
friends: [mongoose.Types.ObjectId],
accessToken: { type: String } // Used for Remember Me
});
var listSchema = new mongoose.Schema({
name: String,
description: String,
contents: [contentSchema],
created: {type: Date, default:Date.now}
});
var contentSchema = new mongoose.Schema({
name: String,
quantity: String,
complete: Boolean
});
exports.User = mongoose.model('User', userSchema);
the friends parameter is defined as an array of Object IDs.
So in other words, a user will have an array containing the IDs of other users. I am not sure if this is the proper notation for doing this.
I am trying to push a new Friend to the friend array of the current user:
user = req.user;
console.log("adding friend to db");
models.User.findOne({'email': req.params.email}, '_id', function(err, newFriend){
models.User.findOne({'_id': user._id}, function(err, user){
if (err) { return next(err); }
user.friends.push(newFriend);
});
});
however this gives me the following error:
TypeError: Object 531975a04179b4200064daf0 has no method 'cast'
If you want to use Mongoose populate feature, you should do:
var userSchema = mongoose.Schema({
email: { type: String, required: true, unique: true},
password: { type: String, required: true},
name: {
first: { type: String, required: true, trim: true},
last: { type: String, required: true, trim: true}
},
phone: Number,
lists: [listSchema],
friends: [{ type : ObjectId, ref: 'User' }],
accessToken: { type: String } // Used for Remember Me
});
exports.User = mongoose.model('User', userSchema);
This way you can do this query:
var User = schemas.User;
User
.find()
.populate('friends')
.exec(...)
You'll see that each User will have an array of Users (this user's friends).
And the correct way to insert is like Gabor said:
user.friends.push(newFriend._id);
I'm new to Mongoose myself, so I'm not entirely sure this is right. However, you appear to have written:
friends: [mongoose.Types.ObjectId],
I believe the property you're looking for is actually found here:
friends: [mongoose.Schema.Types.ObjectId],
It may be that the docs have changed since you posted this question though. Apologies if that's the case. Please see the Mongoose SchemaTypes docs for more info and examples.
I would try this.
user.friends.push(newFriend._id);
or
friends: [userSchema],
but i'm not sure if this is correct.

Categories