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 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 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.
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
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
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:
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 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...
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...
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:
...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:
...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
viewReplies
HTTP method: GET
url: /comments/:id/replies
Here, we want to find all comments where replyParent == id
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
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...
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.
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!
Top comments (0)