Lets build a HackerNews clone in <= 30 minutes
Tags: tutorial, tooling, react, mongodb
Published: 2022-11-03 07:49:46

## A MERN Stack Tutorial Using Neutrino

### Preface
I love coding, but building CRUD apps and authentication systems can get pretty repetitive over time.

Tools like Ruby on Rails and Django have made this process significantly easier and more enjoyable, however, there is still a lot of repetitiveness I would ideally like to avoid.

[Neutrino](https://www.neutrinoapp.com/) makes it significantly faster to code backend architectures. It gives you a nice, intuitive interface to design your backend system and automatically implement most of the code for you using Node/Express best practices and an MVC architecture. 

If you don't know what [HackerNews](https://news.ycombinator.com/) is, its a Reddit-esque forum built by YCombinator to talk about entrepreneurship.

This tutorial will be demonstrating how to use Neutrino to build a simplified HackerNews clone in under 30 minutes. 

&nbsp;
### What is the MERN Stack
MERN stack refers to MongoDB, Express, React, and Node: 
**Mongo** - A popular no-sql database program
**Express** - A backend JavaScript web application framework 
**React** - A front-end JavaScript library for building user interfaces
**Node** - An open source JavaScript runtime environment

&nbsp;
### MVC Architecture
MVC is an architectural pattern for building software and web applications that consists of 3 parts, the Model, the View, and the Controller

**Model** - handles all data logic and directly interacts with the database. In this case, we will be using MongoDB and Mongoose, which is a library built on top of Mongo that we will use to define our model schema and interact with our Express server
**View** - handles all client side logic, this is the React side of the application and will be what the user interacts with
**Controller** - acts as an interface between the Model and View. It processes all requests, fetches data from the Model to send to the View, and takes in information from the View to update the Model

&nbsp;
### App Overview
#### What we'll be building:
Check out the [demo](https://neutrino-news.web.app/posts)
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](https://github.com/ricardo-agz/NeutrinoNewsFrontend) from GitHub.

&nbsp;
### Getting Started
#### Creating a Neutrino account
Register for a Neutrino account [here](https://app.neutrinojs.dev/register) (don't worry, its free!)
After logging in, you should be prompted to this menu:

![Neutrino Homepage](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/btzau6qnugpwkgtlk755.JPG)

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

&nbsp;
### 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.

&nbsp;
Your Models page should look something like this:

![Model parameters](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h8alepdpxqx55bk8yrue.JPG)

&nbsp;
#### 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.

&nbsp;
Your models page should now look something like this...

![Models page with relations](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/em572rz3xmhy2ganq1vs.JPG)


&nbsp;
### 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7acckd7wiods1hst3non.JPG)

&nbsp;
#### 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z2i6ktvomu268sziuht6.JPG)

...Do the same for unlikePost.

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


&nbsp;
**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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nkrkg9ic26jfh37thhqh.JPG)

...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}`);
    };
  },
```

&nbsp;
**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 },
```


&nbsp;
####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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yfb6x6fbgcp9ul8lo9su.JPG)

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

![viewReplies code blocks](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fzzt730isccf50rgf406.JPG)

&nbsp;
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}`);
    };
  },
```


&nbsp;
### Authentication
Neutrino makes it very simple to include JWT authentication out of the box.

Navigate to _Auth_ and click on Simple Authentication

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/drubtcnqqziz9ardwhcp.JPG)

&nbsp;
This will generate an Auth Controller as well as _logIn_, _register_ routes, and a _verifyJWT_ middleware.


&nbsp;
### 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jq5o8gqdlbp5c9bh6nv9.JPG)

&nbsp;
### 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. 


&nbsp;
### Finishing Thoughts

Okay there's a chance after reading everything and probably making some changes, this project took a little more than 30 minutes.
But really, we had the bulk of our backend functionality up and running in only a couple of minutes using Neutrino.

There's obviously a lot more that can be done fixing up the app and adding more features, but hopefully after following these examples, you gathered enough on your own to get started with the rest.

Happy Coding!