I need some input on schema design with mongodb and mongoose.
I have the following scenario data entities:
Posts
Comments
Users
Requirements:
A user can make a comment on a post.
Comments can be served for a post.
A list of all comments of a user can be retrieved.
I'm thinking to make all 3 of them a seperate schema and connect them by using ref.
I see two approaches here and need input on what might be smarter. Should every entity link to its relations or is it enough if only the comments are a "link" to the other data objects?
export const commentSchema = new Schema<CommentDocument>({
content: { type: String, required: true },
userId: { required: true, type: Schema.Types.ObjectId, ref: "user" },
postId: { type: Schema.Types.ObjectId, ref: "post" },
});
And then for both user and post, should they also link to the comment again or is it enough if the relationship is stored once in comment?
export const userSchema = new Schema<UserDocument>({
// ... all my user data
comments: [{ type: Schema.Types.ObjectId, ref: "comment" }] // <--- Is the referencing on the other documents useful?
});
export const postSchema = new Schema<PostDocument>({
// ... all my post data
comments: [{ type: Schema.Types.ObjectId, ref: "comment" }] // <--- Is the referencing on the other documents useful?
});
Is there any rule of thumb for declaring the references between the documents? Is this generally a good schema design approach?
your commentSchema is good
But no need to store commentsId in userSchema& postSchema.
Because, it will be good for fews comment but for large no of comments it is not a good approach.
Related
Last day of 2021! After spending just over 5 hours scouring the internet I've resorted to posting for help.
Desired Behaviour:
the list of documents returned by the route will contain populated documents instead of only the ref ids
Attempted Resolutions:
added exec function after populate
Stackoverflow lead me down a rabbit hole of believing the issue was related to mongoose connections, and so I revamped the backend so that my model files returned schemas instead of models, and then used mongoose.createConnection instead of .connect, and made a db.js module to hold a single instance of each model (connection.mode()) that route files could then access and share. I did not reach a point where I could test .populate so I git reset back to the below code
Error
MissingSchemaError: Schema hasn't been registered for model "progressions".
Use mongoose.model(name, schema)
Note that this error is thrown when trying to populate "lesson" as well
Progress Model
const Progress = mongoose.model('Progress', new Schema({
lesson: {
type: Schema.Types.ObjectId,
ref: 'lesson'
},
progressions: [{
type: Schema.Types.ObjectId,
ref: 'progression'
}],
progress:{
type: Number
},
user:{
type: Schema.Types.ObjectId,
ref: 'user'
}
},{
timestamps: true,
}));
module.exports = Progress;
Progress Routes File
router.get('/all', async (req, res) => {
let user = await User.find({email:req.query.user})
let data = await Progress.find({user:user}).populate('progressions')
return res.status(201).json({
data,
message: 'data populated',
success: true
});
});
Progression Model
const Progression = mongoose.model('Progression', new Schema({
type:{
type:String
},
toBeReviewed:{
type: Date,
default: Date.now
},
},{
timestamps: true,
}));
module.exports = Progression;
Try to change the progressions property ref attribute in the Progress schema to match the Progression model name (capitalize it):
progressions: [{
type: Schema.Types.ObjectId,
ref: 'Progression'
}]
All code in your mongoose files should have run before it is used in other files.
This error could be happening because your mongoose models aren't executed before they are used in some other file.
did you try using "Progressions" while passing it in populate ?
I know this post is late, but for anyone is still having errors. Try using the model instead
import {progressionModel} from "../Progression.model";
progressions: [{
type: Schema.Types.ObjectId,
ref: progressionModel
}]
What is the best way to model retweet schema in MongoDB? It is important that I have createdAt times of both original message and the time when retweet occurred because of pagination, I use createdAt as cursor for GraphQL query.
I also need a flag weather the message itself is retweet or original, and id references to original message and original user and reposter user.
I came up with 2 solutions, first one is that I keep ids of reposters and createdAt in array in Message model. The downside is that I have to generate timeline every time and for subscription its not clear what message to push to client.
The second is that I treat retweet as message on its own, I have createdAt and reposterId in place but I have a lot of replication, if I were to add like to message i have to push in array of every single retweet.
I could use help with this what is the most efficient way to do it in MongoDB?
First way:
import mongoose from 'mongoose';
const messageSchema = new mongoose.Schema(
{
text: {
type: mongoose.Schema.Types.String,
required: true,
},
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true,
},
likesIds: [{ type: mongoose.Schema.Types.ObjectId, ref: 'User' }],
reposts: [
{
reposterId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
},
createdAt: { type: Date, default: Date.now },
},
],
},
{
timestamps: true,
},
);
const Message = mongoose.model('Message', messageSchema);
Second way:
import mongoose from 'mongoose';
const messageSchema = new mongoose.Schema(
{
text: {
type: mongoose.Schema.Types.String,
required: true,
},
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true,
},
likesIds: [{ type: mongoose.Schema.Types.ObjectId, ref: 'User' }],
isReposted: {
type: mongoose.Schema.Types.Boolean,
default: false,
},
repost: {
reposterId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
},
originalMessageId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Message',
},
},
},
{
timestamps: true,
},
);
const Message = mongoose.model('Message', messageSchema);
export default Message;
Option 2 is the better choice here. I'm operating with the assumption that this is a Twitter re-tweet or Facebook share like functionality. You refer to this functionality as both retweet and repost so I'll stick to "repost" here.
Option 1 creates an efficiency problem where, to find reposts for a user, the db needs to iterate over all of the repost arrays of all the messageSchema collections to ensure it found all of the reposterIds. Storing ids in mongo arrays in collection X referencing collection Y is great if you want to traverse from X to Y. It's not as nice if you want to traverse from Y to X.
With option 2, you can specify a more classic one-to-many relationship between messages and reposts that will be simpler and more efficient to query. Reposts and non-repost messages alike will ultimately be placed into messageSchema in the order the user made them, making organization easier. Option 2 also makes it easy to allow reposting users to add text of their own to the repost, where it can be displayed alongside the repost in the view this feeds into. This is popular on facebook where people add context to the things they share.
My one question is, why are three fields being used to track reposts in Option 2?
isReposted, repost.reposterId and repost.originalMessageId provide redundant data. All that you should need is an originalMessageId field that, if not null, contains a messageSchema key and, if null, signifies that the message is not itself a repost. If you really need it, the userId of the original message's creator can be found in that message when you query for it.
Hope this helps!
I have a mongoose model that looks something like this
var LogSchema = new Schema({
item: {
type: ObjectId,
ref: 'article',
index:true,
},
});
But 'item' could be referenced from multiple collections. Is it possible to do something like this?
var LogSchema = new Schema({
item: {
type: ObjectId,
ref: ['article','image'],
index:true,
},
});
The idea being that 'item' could be a document from the 'article' collection OR the 'image' collection.
Is this possible or do i need to manually populate?
Question is old, but maybe someone else still looks for similar issues :)
I found in Mongoose Github issues this:
mongoose 4.x supports using refPath instead of ref:
var schema = new Schema({
name:String,
others: [{ value: {type:mongoose.Types.ObjectId, refPath: 'others.kind' } }, kind: String }]
})
In #CadeEmbery case it would be:
var logSchema = new Schema({
item: {type: mongoose.Types.ObjectId, refPath: 'kind' } },
kind: String
})
But I did't try it yet...
First of all some basics
The ref option says mongoose which collection to get data for when you use populate().
The ref option is not mandatory, when you do not set it up, populate() require you to give dynamically a ref to him using the model option.
#example
populate({ path: 'conversation', model: Conversation }).
Here you say to mongoose that the collection behind the ObjectId is Conversation.
It is not possible to gives populate or Schema an array of refs.
Some others Stackoverflow people asked about it.
Soluce 1: Populate both (Manual)
Try to populate one, if you have no data, populate the second.
Soluce 2: Change your schema
Create two link, and set one of them.
var LogSchema = new Schema({
itemLink1: {
type: ObjectId,
ref: 'image',
index: true,
},
itemLink2: {
type: ObjectId,
ref: 'article',
index: true,
},
});
LogSchema.find({})
.populate('itemLink1')
.populate('itemLink2')
.exec()
Dynamic References via refPath
Mongoose can also populate from multiple collections based on the value of a property in the document. Let's say you're building a schema for storing comments. A user may comment on either a blog post or a product.
body: { type: String, required: true },
on: {
type: Schema.Types.ObjectId,
required: true,
// Instead of a hardcoded model name in `ref`, `refPath` means Mongoose
// will look at the `onModel` property to find the right model.
refPath: 'onModel'
},
onModel: {
type: String,
required: true,
enum: ['BlogPost', 'Product']
}
});
const Product = mongoose.model('Product', new Schema({ name: String }));
const BlogPost = mongoose.model('BlogPost', new Schema({ title: String }));
const Comment = mongoose.model('Comment', commentSchema);
I define the comment model as:
CommentSchema = new mongoose.Schema ({
name: String,
email: String,
website: String,
content: String,
createDate: Date,
updateDate: Date,
targetBlog: {type: mongoose.Schema.Types.ObjectId, ref: 'Blog'},
childrenComment: [{type: mongoose.Schema.Types.ObjectId, ref: 'Comment'}]
});
And when I use populate as:
Comment.find({targetBlog: blog._id}).populate({path: 'childrenComment'}).exec(function(err, comments) {
console.log(comments);
res.render('blog', { blog: blog, comments: comments});
});
I find mongoose only populate one level deep. So how can I do to make it populate more than one level, because the level can be 2 or 3 or more.
You can specify the model manually when populating.
Comment.find({targetBlog: blog._id})
.populate({path: 'childrenComment', model: 'Comment'})
.exec(function(err, comments) {
console.log(comments);
res.render('blog', { blog: blog, comments: comments});
});
Update:
It looks like your code is working without adding the model. So the problem should be somewhere else, not in the populating. This is what you are trying to do, right?
I'm trying to locate a reference to another schema and update a sibling field. Specifically, I'm trying to manipulate the hasResponded field below based on a particular 'survey' ObjectId.
My schema looks like this:
var userSchema = new mongoose.Schema({
// some other stuff
surveys: [{
survey: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Survey'
},
hasResponded: {
type: Boolean,
default: false
}
}]
});
If you have a survey id, simply search for all the users that have this particular id in the array.
Something like that:
Users.find({surveys: { "$elemMatch": { type: <ID> } } });
Then, iterate through the users and their corresponding survey array to find the ones that match the id you gave.
Got to say I would structure this db a little different if this query takes place often.
Make a new Schema - UserSurveys that holds the id of the user and the survey + hasResponded. Like this:
var UserSurveySchema = new mongoose.Schema({
user_id: {type: mongoose.Schema.Types.ObjectId, ref: 'User'},
survey_id: {type: mongoose.Schema.Types.ObjectId, ref: 'Survey'}
hasResponded: {type:Boolean, 'default':false}
...
});
You might also want to keep an index on the user and survey ids.
Then, it will be much easier to update the field, requests will take much shorter times.
Hope this helps.