8. Associate Posts and Comments with User
Alright next step! Its time to allow people to take responsibility for the silly things they are posting on our Reddit clone. We are going to create user accounts so folks can securely signup and login using a username and password.
- Create a post
- Show all posts
- Show one post
- Comment on posts
- Create subreddits
- Sign up and Login
- Association posts and comments with their author
- Check authentication and make
req.userandcurrentUserobjects - Add
authorattribute to comments and posts - Save the user as the author of posts
- Display the author's username on posts and comments
- Check authentication and make
- Make comments on comments
- Vote a post up or down
Checking Login Status
So now that we are setting the cookie to be logged in, and can see this cookie on the server or client side, how do we customize our views and the authorization of a user? There are a few ways to do it. We could use client JavaScript to track the presence of the nToken cookie and update the DOM based on that. But let's try to find a server-side solution first because our app is very server-side structured now.
We can always check if req.cookies.nToken is present, but shouldn't we also check if it is valid? And don't we really want the _id of the user this token represents? This is a lot of code to include in every route! In order to refactor this code, we can make our own custom middleware.
[action] Create a new folder
middleware. In that folder create the filecheckAuth.js.
const jwt = require('jsonwebtoken');>const checkAuth = (req, res, next) => { console.log('Checking authentication'); if (typeof req.cookies.nToken === 'undefined' || req.cookies.nToken === null) { req.user = null; } else { const token = req.cookies.nToken; const decodedToken = jwt.decode(token, { complete: true }) || {}; req.user = decodedToken.payload; }> next();};>module.exports = checkAuth;We can now import the middleware into
server.jsand use it as a middleware before every route. Be sure to placeapp.use(checkAuth);before the controllers.
...const checkAuth = require('./middleware/checkAuth');...app.use(checkAuth);...Go to any route and see if the Checking authentication is logged in the terminal.
Note: this is your own custom middleware like body-parser and cookie-parser. Middleware is run in the order it is added via app.use(). You must add middleware after initializing Express, and the order will matter.
Updating Templates
Once our checkAuth middleware is running on every route, let's use the new req.user object we created to create a currentUser object. We'll use this currentUser object to hide the login and sign up links if a user is logged in.
[action] Update your
navbarto include the logic aroundcurrentUser:
<ul class="nav navbar-nav navbar-right"> {{#if currentUser}} <li><a href="/logout">Logout</a></li> {{else}} <li><a href="/login">Login</a></li> <li><a href="/sign-up">Sign Up</a></li> {{/if}}</ul>Now in any route we can set currentUser equal to req.user which will either be { _id: <<ID>> } or null.
[action] Update the
INDEXmethod in yourpostscontroller to includecurrentUser:
app.get('/', (req, res) => { const currentUser = req.user;> Post.find({}) .then((posts) => res.render('posts-index', { posts, currentUser })) .catch((err) => { console.log(err.message); });});[challenge]
Refactor the code block above to be async/await.
Do the login and sign up links appear and disappear depending upon whether a user is logged in?
Now, hide the "New Post" button for those who are NOT logged in.
[action] Update your
navbaragain to hide the "New Post" button if a user is not logged in:
... ><ul class="navbar-nav mr-auto"> {{#if currentUser}} <li class="nav-item"> <a class="nav-link" href="/posts/new">New Post</a> </li> {{/if}}</ul>> ...Remember, you'll have to add currentUser to all of the routes that call res.render() so the main templates work. This may seem like some duplication of code, and it is.
Requiring the User to Be Logged In to Post
Right now, if you aren't logged in, you could still just navigate to /posts/new and create a post. Let's be a bit more secure and prevent someone from creating a post unless they are logged in.
[action] Update your
CREATEmethod in thepostscontroller to the following:
// CREATEapp.post('/posts/new', (req, res) => { if (req.user) { const post = new Post(req.body);> post.save(() => res.redirect('/')); } else { return res.status(401); // UNAUTHORIZED }});[challenge]
Refactor the code block above to be async/await.
Product So Far
Logged in Vs logged out users should now have different views:
Logged Out

Logged In

We could do this more elegantly and more DRY if we made another example of custom middleware called CheckAuth and used it on those routes that we require to be logged in.
Can you rewrite the above code into its own middleware called CheckAuth?
Now Commit
$ git add .$ git commit -m 'Users must be logged in to submit a post'$ git pushThrough the code above, you have now successfully restricted functionality based on authentication status. Right on!
Associating the author of Comments and Posts
So now we want people to take responsibility for their silly posts on Reddit.js.
We need to make each post and each comment point back to its author, as well as ensure the posts and comments are persisted with associations to the user that creates them. We'll do this by saving the author's user id in each child post or comment, and by tracking the post and comment id's for each user. Then we can use the .populate() method to pull in the details whenever we need.
To accomplish this, there are no changes required to the views we've already created. We can go straight to updating model and controller appropriately.
[action] First, let's add an
authorattribute to both themodels/comment.jsand themodels/post.jsfiles. Its type will be a singleObjectId. We'll make it required because only logged in people can create posts.
...author: { type: Schema.Types.ObjectId, ref: 'User', required: true },...Additionally, add the
postsattribute to theUsermodel. It will be an array ofObjectIds.
...posts: [{ type: Schema.Types.ObjectId, ref: 'Post' }],...Now we can update the posts controller to save the current user as the author when we create a post,
and we can look up the current user and add the new post to their posts.
[action] Update the
CREATEmethod of yourpostscontroller to the following. Remember to include theUsermodel as a new requirement:
const Post = require('../models/posts');const User = require('../models/user');const Comment = require('../models/comment');>module.exports = (app) => {... // CREATE app.post('/posts/new', (req, res) => { if (req.user) { const userId = req.user._id; const post = new Post(req.body); post.author = userId;> post .save() .then(() => User.findById(userId)) .then((user) => { user.posts.unshift(post); user.save(); // REDIRECT TO THE NEW POST return res.redirect(`/posts/${post._id}`); }) .catch((err) => { console.log(err.message); }); } else { return res.status(401); // UNAUTHORIZED } });...}[challenge]
Refactor the code block above to be async/await.
Test that both the author and the posts are being saved by looking in your database or logging to the console.
Now, can you do this same pattern for the comments controller when someone creates a comment?
Displaying the Author
Next, populate the author in posts and display their username on every post wherever it appears.
Let's start by just getting the author field to appear on the posts you see on the home page.
[action] Add the
authorbelow theurlinposts-index:
<div><small>{{this.author}}</small></div>If you refresh right now, you'll just see an ObjectId for the author. We need to populate the field!
[action] >
populatetheauthorfield from theINDEXmethod in thepostscontroller:
// INDEXapp.get("/", (req, res) => { const { user } = req console.log(req.cookies) Post.find({}) .lean() .populate("author") .then((posts) => res.render("posts-index", { posts, user })) .catch((err) => { console.log(err.message) })})[challenge]
Refactor the code block above to be async/await.
Product So Far
Now you should be seeing a User object! We're almost there, just update your posts-index to pull from the username field within author
[action] Update the
authordiv inposts-index:
<div><small>{{this.author.username}}</small></div>
Authors - Single Post
Now we have authors properly displayed on the home page! We still need to get authors to display when looking at a single post, and when looking at a specific subreddit.
Let's work on the single post first, this should be very similar to what we just did for the home page.
[action] Update
posts-showto include theauthor:
<p>{{post.author.username}}</p>Update
SHOWin thepostscontroller topopulatethe author:
// LOOK UP THE POSTapp.get('/posts/:id', (req, res) => { const currentUser = req.user;> Post.findById(req.params.id).lean().populate('comments').populate('author') .then((post) => res.render('posts-show', { post, currentUser })) .catch((err) => { console.log(err.message); });});Note we did a double call to populate in order to get both fields!
Finally, let's get author showing for posts on a subreddit.
[action] Update the
SUBREDDITmethod inpostscontroller to display theauthoron posts:
// SUBREDDITapp.get("/n/:subreddit", (req, res) => { const currentUser = req.user const { subreddit } = req.params Post.find({ subreddit }) .lean() .populate("author") .then((posts) => res.render("posts-index", { posts, currentUser })) .catch((err) => { console.log(err) })})[challenge]
Refactor all the code blocks above to be async/await.
Now Commit
$ git add .$ git commit -m 'Authors for posts are visible'$ git pushNow for Comments
Using the previous instructions for associating users and posts, can you make it so users and comments are equally associated? Remember to update the corresponding view and controller files!
Hint: You should review the documentation for Mongoose's populate method. Also look in to how you would nest populate calls.
[solution] Create the template
post-commentsand includeauthorin each comment:
{{#each post.comments}}<p>{{this.content}}</p><p class="text-right">{{this.author.username}}</p>{{/each}}Update
commentscontroller to include theauthor
...const comment = new Comment(req.body);comment.author = req.user._id;...Update the
SHOWmethod in thepostscontroller topopulatetheauthorof thecomments:
// SHOWapp.get('/posts/:id', function (req, res) { const currentUser = req.user; // LOOK UP THE POST> Post.findById(req.params.id).lean().populate({ path:'comments', populate: { path: 'author' } }).populate('author') .then((post) => res.render('posts-show', { post, currentUser })) .catch((err) => { console.log(err.message); });});Product So Far
Authors should now be displayed for both Posts and Comments. Try posting as one user, and commenting as another to see it in action:

Now Commit
$ git add .$ git commit -m 'Authors for comments are visible'$ git pushStretch Challenges
[challenge]
- Can you make an author's username a link that displays that users's profile at
/users/:username?- Can you do the same for comments?
- Can you make a
/profileroute that loads the current user and displays their posts and comments?