Joins in Meteor.js and MongoDB

08-04-2015 (d-m-Y)

One of the most asked beginner's question is: How to join collections?

Let's look at an example: We have Events collection and Users collection. Users can attend many events and events can have many visitors (users). It's M:N relationship. We can store eventId in Users collection or userId in Events collection. I chose to store userId in Events collection.

Users:

[{
_id: 1,
name: 'Bob'
},
{
_id: 2,
name: 'Josh'
}]

Events:

[{
_id: 1,
name: 'Metallica concert',
users: [ 1, 2 ]
},
{
_id: 2,
name: 'Some reggae festival',
users: [ 1 ]
}]

We can see that Bob and Josh attended Metallica concert. Bob even attended Some reggae festival. Now, how to get user's and event's data? When we define a collection, there is an option transform:

Events = new Mongo.Collection('events', {
  transform: function(doc) {
    doc.usersObj = Users.find({
      _id: { $in: doc.users }
    });
    return doc;
  }
});

Users = new Mongo.Collection('users', {
  transform: function(doc) {
    doc.eventsObj = Events.find({
      users: { $in: [ doc._id ] }
    });
    return doc;
  }
});

The transform option is a function that takes document as a parameter and we can modify it. Transform have to return the document. If we call Events.find(), Events.findOne() we will have access to usersObj property which is Users document. And Users collection will have eventsObj property.

Now, we can just call and return Events.findOne() in our helper:

Template.event.helpers({
  event: function() {
    return Events.findOne();
  }
});

Template:

<template name="event">
<h2>Event: {{event.name}}</h2>
  <h3>Users</h3>
<ul>
  {{#each event.usersObj}}
     <li>{{name}}</li>
  {{/each}}
</ul>
</template>

We iterate through event.usersObj and we can access user's properties inside this loop.

Now, how to get events for user? It's similar to how we get users for events:

Template.user.helpers({
  user: function() {
    return Users.findOne();
  }
});

Template:

<template name="user">
  <h2>User: {{user.name}}</h2>
  <h3>Events</h3>
  <ul>
    {{#each eventsObj}}
      <li>{{name}}</li>
    {{/each}}
  </ul>
</template

Thanks to transform option in collection definition is joining easy.

If you want to know how to work with joins, publications and Iron Router in real project, I have created repo that you can download and run: https://github.com/Elfoslav/meteor-join-collections

For 1:N relations you can usually store related data as sub-document. For example blogpost and comments:

[{
_id: 1,
name: 'Joins in Meteor.js and MongoDB',
comments: [{
_id: 1,
username: 'Foo',
text: 'Great article!'
}, {
_id: 2,
username: 'Bar',
text: 'Amazing!'
}]
},
{
_id: 2,
name: 'Why I love JavaScript and Meteor.js',
comments: []
}]

For more info read Reactive Joins and Official MongoDB docs.

Edit: You can also use Collection helpers package for joins.

 

← All articles | If you want to know about new blogposts you should follow @elfoslav

comments powered by Disqus