Alright next step! We can see those posts, let's comment on them.

  1. Create a post
  2. Show all posts
  3. Show one post
  4. Comment on posts
    1. Make a new comment form in the posts#show template
    2. Make a create route for comments
    3. Associate comments with posts
    4. Display comments
  5. Create subreddits
  6. Sign up and Login
  7. Associate posts and comments with their author
  8. Make comments on comments
  9. Vote a post up
  10. Sort posts by # of votes

New Comment Form (with Nested Route)

Remember, we are always coding using an agile and user-centric approach, so we are going to always start whatever feature from what the user sees and does. Then we'll code back towards what the server and code does.

So, if we want to allow users to comment on these posts, first we can add a comment form to our posts#show page. This form will send its data to the a path that resolves to the comments#create action. The path for this link will follow the standard nested RESTful convention /<<PARENT RESOURCE PLURAL>>/<<PARENT ID>>/<<CHILD RESOURCE PLURAL>>.

Let's use a textarea for the comment attribute body.

...
  <form action="/posts/{{post._id}}/comments" method="post">
    <textarea class='form-control' name="content" placeholder="Comment"></textarea>
    <div class="text-right">
      <button type="submit">Save</button>
    </div>
  </form>

If you submit the form, it fails because there is no POST route to /posts/{{post._id}}/comments yet. Let's fix that working from the outside (what the user sees and does) into our server.

Make Create Comment Route

Now we need a create comment route. We can start with the code we used for the create route for posts.

Remember that a route can be called a number of different names: an endpoint, a webhook, a path, and others.

Follow the pattern you used for the Post resource to create a Comment resource.

  1. Create a comments controller in a new file comments-controller.js in your controllers folder
  module.exports = function(app) {

  };
  1. Export the comments controller into the server.js
    require('./controllers/comments-controller.js')(app);
  1. Make the CREATE in a nested route (hint: /posts/:postId/comments)
// CREATE Comment
app.post('/posts/:postId/comments', function (req, res) {
  // INSTANTIATE INSTANCE OF MODEL
  const comment = new Comment(req.body)

  // SAVE INSTANCE OF Comment MODEL TO DB
  comment.save().then((comment) => {
    // REDIRECT TO THE ROOT
    return res.redirect(`/`)
  }).catch((err) => {
    console.log(err);
  })
})
  1. Create a Comment model in a comment.js file
const mongoose = require('mongoose')
const Schema = mongoose.Schema

const CommentSchema = new Schema({
   content: { type: String, required: true }
})

module.exports = mongoose.model('Comment', CommentSchema)
  1. Require the comment model in the comments controller
  const Comment = require('../models/comment')
  1. Create a comment by submitting your form
  2. Confirm that comments are saving by inspecting the database

Associating Comments and Posts

The Gotcha - the gotcha here is that if you just use the same code from posts.js you will only create comments in their own collection and not associate them with their parent post. We're going to use a Reference Association meaning we will reference the child document by its id in the parent's document. The child's id acts similarly to a Foreign Key in a SQL database. So let's do it.

First we can do the controller logic, then model logic.

In the controller we need find the parent Post from the :postId we have in the url parameters, then associate this parent with the comment by pushing the comment into an array in the parent's attribute comments that we haven't created yet.

// CREATE Comment
app.post('/posts/:postId/comments', function (req, res) {
  // INSTANTIATE INSTANCE OF MODEL
  const comment = new Comment(req.body)

  // SAVE INSTANCE OF Comment MODEL TO DB
  comment.save().then((comment) => {
    return Post.findById(req.params.postId)
  }).then((post) => {
    post.comments.unshift(comment)
    return post.save()
  }).then((post) => {
    res.redirect(`/`)
  }).catch((err) => {
    console.log(err)
  })
})

Why did I recommend we use unshift here instead of push?

unshift adds an element to the front of an array, while push adds it to the end. Reddit puts its newest comments at the top, so we want the default order to be reverse chronological order.

Next we need to add an array attribute to the mongoose Post model.

comments: [{ type: Schema.Types.ObjectId, ref: 'Comment' }]

Finally, create some new comments and confirm that their _id's are being added to this comments attribute.

Displaying Comments

Now that we have the comments associate we can see them in the parent post object. Let's add them to the posts#show template below the new comment form.

{{#each post.comments}}   
  {{this}}
{{/each}}

What do you see?

Just the id's right? When we do a reference association, we only save the id's into the parent's document. In order to replace these id's with the actual child document, we have to use the mongoose function .populate() when we fetch the parent from the database. Like this:

// LOOK UP THE POST
Post.findById(req.params.id).populate('comments').then((post) => {
  res.render('post-show.hbs', { post })
}).catch((err) => {
  console.log(err.message)
})

Now do we see the comments?

Just one more change, you have to access the content attribute of each comment. You can add more style to these if you like. Perhaps a paragraph tag to start.

{{#each post.comments}}   
  <p>{{this.content}}</p>
{{/each}}

Onward!

Feedback

If you have feedback on this tutorial or find any mistakes, please open issues on the GitHub Repository or comment below.

Summer academy

An iOS Development Summer Course

Design, code and launch your own app. Locations in San Francisco and Asia

Find your location

Product College

A computer science college

Graduate into a successful career as a founder or software engineer.

Learn more