Understanding Many-to-Many relationships in MongoDB and how to dereference collections
javascript, mean-stack, mongodb, mongoose
Solution
For a discussion of schema design in MongoDB, covering exactly this topic, I refer you to these postings on the MongoDB blog:
- Part 1
- Part 2
- Part 3
In particular, look at the sample JavaScript code showing you how to do the application-level joins.
Problem
I've spent some time researching MongoDB alternatives for implementing a many-to-many relationships including several stackoverflow articles (here and here) and these slides. I am creating an app using the MEAN stack and I'm trying to get confirmation on my schema setup and best practices in dereferencing a collection of objects. I have a basic many-to-many relationship between users and meetings (think scheduling meetings for users where a user can be in many meetings and a meeting contains several users). Given my use case I think it's best that I use referencing rather than embedding. I believe (from what I've read) that it would be better to use embedding only if my meetings had users unique to a single meeting. In my case these same users are shared across meetings. Also, although updating users would be infrequent (e.g., change username, password) I still feel that using a reference feels right - although I'm open to opinions. Assuming I went with references I have the following (simplified) schema: ``` var MeetingSchema = new Schema({ description: { type: String, default: '', required: 'Please fill in a description for the meeting', trim: true }, location: { type: String, default: '', required: 'Please fill in a location for the meeting', trim: true }, users: [ { type: Schema.ObjectId, ref: 'User' } ] }); var UserSchema = new Schema({ firstName: { type: String, trim: true, default: '', validate: [validateLocalStrategyProperty, 'Please fill in your first name'] }, lastName: { type: String, trim: true, default: '', validate: [validateLocalStrategyProperty, 'Please fill in your last name'] }, email: { type: String, trim: true, default: '', validate: [validateLocalStrategyProperty, 'Please fill in your email'], match: [/.+\@.+\..+/, 'Please fill a valid email address'] }, username: { type: String, unique: true, required: 'Please fill in a username', trim: true }, password: { type: String, default: '', validate: [validateLocalStrategyPassword, 'Password should be longer'] } }); ``` First, you will notice that I don't have a collection of meetings in users. I decided not to add this collection because I believe I could use the power of a MongoDB find to obtain all meetings associated with a specific user - i.e., ``` db.meetings.find({users:ObjectId('x123')}); ``` Of course I would need to add some indexes. Now if I'm looking to deference my users for a specific meeting, how do I do that? For those who understand rails and know the different between :include and :join I'm looking for a similar concept. I understand we are not dealing with joins in MongoDB, but for me in order to dereference the users collection from the meeting to get a user's first and last name I would need to cycle through the collection of id's and perform some sort of a db.users.find() for each id. I assume there's some easy MongoDB call I can make to get this to occur in a performant way.