Abe Massry

Web Development and Building Businesses

Creating and Displaying Subdocs and Nested Data With Mongodb and Nodejs

If I was using MySQL and creating tables for a relational database I have a couple of ways to show comments and likes for an article on a blog for example. The first way is to select my post from a table and then based on that ID of the post select my comments and likes for that post. If I have properly normalized my tables this works well. The problem with this is requires extra processing in my application code. The next way is to use a join and write MySQL code to join the tables where there is a match and output the results. This is ok but it requires more MySQL processing.

The question becomes how to do this in mongodb, a document oriented database without tables. This turns out to be simpler to do and understand than the MySQL equivalent.

1. Set up your document Schema

For this code I’m using Node.js and mongoose to talk to mongodb, and jade to display.

Set Up Schema - schema.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
  var Likes = new Schema({
    like_id: ObjectId,
    name: String,
    date_liked: Date,
  });
  exports.Likes = mongoose.model('Likes', Likes);

  var Comments = new Schema({
    comment_id: ObjectId,
    user: String,
    user_photo: String,
    body: String,
    date_commented: Date,
  });
  exports.Comments = mongoose.model('Comments', Comments);

  var schema = new Schema({
    blog_id: ObjectId,
    body: String,
    lang: String,
    title: String,
    date: Date,
    submitted_by: String,
    submitted_by_photo: String,
    likes: [Likes],
    comments: [Comments]
  });

The main schema is the “var schema” this is the blog post. The blog post contains likes and comments, of each there will be many. Those are stored in objects and there can be multiple objects in each of the likes and comments. The advantage of this is that you can make one database call and get all the information about a post at once, so you need less application to database communication. And it is also easier to understand when you think about this. For example you can add a tag schema store in another object and it can have an arbitrary number of tags.

2. Reading nested Data out from mongodb

Here I’m using an express app to read the data out and send it to a jade file for displaying.

Reading data from mongodb in nodejs using mongoose - read.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
app.get('/blog/:id', function(req, res){
    if (req.user) {
      var username = req.user.username;
    }
    //get the example
    models.posts.findById(req.params.id, function(err, data){
      if (data) {
        models.users.findOne({name: username}, function(err, userdata){
         //render the view page
         res.render('blog.jade', {
             locals: {
               title: data.title,
               page: '',
               article: data,
               user: username,
               userdata: userdata
             }
         });
        });
      } else {
        res.redirect('/404');
      }
    });
  });

There are no differences here reading from a mongodb doc that has nested data vs non nested data.

3. Output the data in jade.

There are two each loops that run through the nested data. If there are multiple nested data on a page, like the main page of the blog, then you can loop through the blog articles and then have a sub loop for each of the comment sections.

Displaying nested data in jade - blog.jade
1
2
3
4
5
6
7
- each like in article.likes
  - i++;
button.btn.like(type="button", id="like_#{article.id}")
  i.icon-thumbs-up
  | Like
- each comment in article.comments
  - j++;

4. Posting data to mongodb

In order for the app to be useful there has to be some way for users to like and comment on articles. Here is an example of an express route for commenting on an article.

comment on an article express route - comment.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
app.post('/comment', function(req, res){
    if (req.user) {
      models.users.findOne({name: req.user.username}, function(err, userdata){
        var picture = userdata.photo;
        var now = new Date();
        blog_id = req.param('blog_id');
        models.posts.findById(blog_id, function(err, blog){
          if (err) return handleError(err);
          blog.comments.push({user: req.user.username, user_photo: picture, body: req.param('comment'), date_commented: now});
          var doc = blog.comments[0];
          console.log(doc);
          blog.save(function(err) {
            console.log('error check');
            if(err) { throw err; }
            console.log('saved');
            res.redirect('/blog/'+blog_id);
          });
        });
      });
    }
  });

If you see an area where the code can be improved let me know. I’d like to incorporate all of this into a framework at some point.

Comments