Tutorials

Making a HackerNews clone in < 30 minutes using Neutrino

 

App Overview

What we'll be building:

Check out the demo We will build a forum where users can sign up, log in/out, create posts, like posts, comment on posts, and reply to comments. The app should also include a front page where you can view all posts and click on individual posts to view all comments. This is strictly a backend tutorial but if you want to follow along, feel free to clone the frontend code from GitHub.

 

Getting Started

Creating a Neutrino account

Register for a Neutrino account here (don't worry, its free!) After logging in, you should be prompted to this menu:

Neutrino Homepage

Click on 'start from an empty project'...

 

Setting Up the Models

Let's start the project by defining the models we're going to use. In the context of HackerNews NeutrinoNews, we're going to need Users, Posts, and Comments.

User

Users will have a username (string), email (string), and password (string).

Post

Posts should have a title (string), url (string), and content (string).

Comment

Users to have content (string) and a user (Object Id).

ℹ️ Make sure to leave 'initialize with with controller' checked when adding the Models.

  Your Models page should look something like this:

Model parameters

 

Model Relationships

We want to define our database relationships to handle features such as likes and posting.

A user should be able to make posts, therefore User has many Posts and a Post belongs to User.

A user should be able to comment on posts. With this, there are a couple implementations, but for this tutorial, Post has many Comment and Comment belongs to Post. Remember we added a user parameter of type Object Id for Comment.

Implementing Likes

Likes will be implemented as a many-to-many relationship between User and Post. That is, a User has many Post (likedPosts), and Post has many User (likedBy).

Implementing Replies

We will add an additional, optional parameter to Comment called replyParent, which will be the Object Id of the parent Comment if the comment is a reply.

  Your models page should now look something like this...

Models page with relations

 

API Routes

Navigate to the Routes page. If you left the checkbox selected when creating the models through the modal, then Neutrino should have automatically implemented all the CRUD routes for User, Post, and Comment. That being said, there are still a couple of changes we need to make.

Your Routes page should look something like this...

Routes page initial

 

Liking Posts

We want to create 2 functions for the User controller:

  • likePost
  • unlikePost

likePost HTTP method: POST url: /users/:id/like-post/:post_id

unlikePost HTTP method: POST url: /users/:id/unlike-post/:post_id

You can implement these functions by clicking on add route_ as follows:

Image description

...Do the same for unlikePost.

We can now write out the logic functions by clicking on the orange gear icon by the route.

  Logic Liking posts works as follows:

  • We find the User with _id = id, and push post_id to its likedPosts array.

  • We then find the Post with _id = post_id and push id to its likedBy array.

  • We then return a success message or an error if anything didn't work.

We can do this pretty easily using the code blocks as follows:

logic blocks for likePost

...or if you'd rather code it yourself, this is what the code would look like using Mongoose:

likePost: async (req, res) => {
    try {
      const { id, post_id } = req.params;
      User.findByIdAndUpdate(id,
      {
        $push: { likedPosts: post_id }
      },
      (err, data) => {
        if (err) {
          return res.status(500).send({ message: "Error updating user" });
        };
        Post.findByIdAndUpdate(post_id,
        {
          $push:  { likedBy: id },
        },
        (err2, data2) => {
          if (err2) {
            return res.status(500).send({ message: "Error updating post" });
          };
          return res.status(200).send({ message: "Successfully liked post and added user to post likes" });
        });
      });
    } catch(e) {
      console.error(`server error in UserController likePost() : ${e}`);
    };
  },

  unlikePost This function will be exactly the same except that instead of pushing, you will be pulling.

i.e.

$push:  { likedBy: id },

will instead be:

$pull:  { likedBy: id },

 

Creating Replies

We will write two functions for the Comment Controller:

  • createReply
  • viewReplies

createReply HTTP method: POST url: /comments/:id/create-reply Here, we want to create a new Comment, and pass in the additional replyParent = id parameter

createReply code blocks

viewReplies HTTP method: GET url: /comments/:id/replies Here, we want to find all comments where replyParent == id

viewReplies code blocks

  If you are interested in coding them yourself, the code using Mongoose would look as follows...

/*
   * createReply
   * url: /comments/:id/create-reply
   * params: ['id', 'reply_id']
   */
  createReply: async (req, res) => {
    try {
      const { id } = req.params;
      const { content, user, post } = req.body;
      const newReply = await new Comment({
        content,
        user,
        post,
        replyParent: id,
      }).save((err, data) => {
        if (err) {
          return res.status(500).send({ message: "Error creating comment" });
        };
        return res.status(200).send({ message: "Successfuly created reply comment" });
      });
    } catch(e) {
      console.error(`server error in CommentController createReply() : ${e}`);
    };
  },

  /*
   * view replies
   * url: /comments/:id/replies
   */
  viewReplies: async (req, res) => {
    const { id } = req.params
    try {
      const replies = await Comment.find({ replyTo: id });
      return res.status(200).send(replies);
    } catch(e) {
      console.error(`server error in CommentController index() : ${e}`);
    };
  },

 

Authentication

Neutrino makes it very simple to include JWT authentication out of the box.

Navigate to Auth and click on Simple Authentication

Image description

  This will generate an Auth Controller as well as logIn, register routes, and a verifyJWT middleware.

 

Security

Middlewares

Let's say we want to reserve a few features for only users that have signed in.

We can go back to the Routes page and add the verifyJWT middleware to the route to block a non-signed in user from calling the function.

ex. For creating, editing, and deleting posts, we can add the verifyJWT middleware as follows...

Image description

 

Exporting your project

By navigating to the Export tab, you can download your code as a Node, MongoDB, Express project following an MVC architecture, that should hopefully feel a bit intuitive to work on top off.

 

Happy Coding!

Previous
Neutrino Guide