Lets Build a Twitter Clone in <= 30 minutes
Tags: tutorial, tooling, react, mongodb
Published: 2022-04-04 07:18:29

## A MERN Stack Tutorial Using Neutrino JS


## Preface
I've been building web and mobile apps for a couple of years now, and what I hate the most has consistently been the hassle of starting a new project. 
It takes hours of coding and debugging just to get to a pretty generic point of in the application's process. 

I came across Ruby on Rails around a year and a half ago and it pretty quickly became my go-to when creating new web apps. 
Its CLI and generators are pretty unmatched when trying to build a prototype as quickly as possible, while completely eliminating the need to write any boilerplate code.

I recently came across this pretty cool dev tool called Neutrino, that's basically an online web builder for MERN stack web apps. 
I'd call it a glorified GUI for what would be the equivalent of Rails's generators, but it would really be doing it a disservice since it really is quite a bit more powerful. 

This tutorial will be demonstrating how to use Neutrino to build a simplified Twitter 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;
### Getting Started
Neutrino uses MongoDB to power its database, in this tutorial, we will be using MongoDb Atlas, but you could run MongoDB locally as well if you wanted.
We won't go over how to set up a new MongoDB Atlas cluster and database, but you can follow [this tutorial](https://www.mongodb.com/basics/mongodb-atlas-tutorial) to get started.
- Once you've created your cluster and set up your database, you're going to want to get your [connection string](https://docs.mongodb.com/guides/cloud/connectionstring/).

- You're also going to want to make sure you have Node js installed to run your application.

&nbsp;
### Setting Up the Models
First, let's start a new Neutrino project at [app.neutrinojs.dev](https://app.neutrinojs.dev/)

If you're curious and want to check out their documentation, you can find it at [neutrinojs.dev/docs](https://neutrinojs.dev/docs)

#### User
We want users to be able to have a name, username, and bio, as well as be able to register to our app, which will require us to define email and password parameters as well.

So, our params will be:
- name - string
- username - string
- email - string
- password - string
- bio - text

**note:** Here, we differentiate string from text, but text is nothing more than a string without character limits. It will also default to a _textarea_ component in React.

#### Post
We want users to be able to make posts, and posts to contain a few different parameters, such as the number of likes, title, content, and comments.
- We could build comments right into the post, but it would be much better practice to separate them out into their own model with their own CRUD functionality.
- Similarly for likes, we could build them right into a number parameter for post, but what if we want to access the people who liked the post? Or get a list of all the posts a user has liked? We'd need more than a simple number keeping track of the number of times a user has pressed 'like'. We will go over this later.

Therefore, our data parameters will look like this:
- title - string
- content - text

#### Comment
We want users to be able to comment on different posts, and we want these comments to be associated with the user that posted them.

Therefore, out data parameters will look like this:
- content - string
- user - string (actually, this will be a Mongoose id, but we will discuss this later) 

&nbsp;
### Defining Model Relations
When we discuss model relations, we're going to bring up terminology such as one-to-many or many-to-many, these are terms typically used in SQL databases, and the meaning doesn't really apply in the same way as it would in an SQL database. Nevertheless, they are still effective at conveying the hierarchy of our models and how they will interact with each other. 

- We want users to be able to make posts, therefore, a user will be able to have many posts, but a post will belong to only one user. This is a one-to-many relationship between users and posts.

- We also want users to be able to comment on different posts. Therefore, a post can have many comments, but a comment can only belong to one post. This again, is a one-to-many relationship between posts and comments. 

- By extension, we can also represent a one-to-many relationship between users and comments, however, Neutrino currently doesn't support a multiple one-to-many relationships for the same 'many' model, so we will just have to do this manually.

**To summarize:**
- a user _has many_ posts
- a post _belongs to_ a user
- a post _has many_ comments
- a comment _belongs to_ a post

&nbsp;
### Implementing in Neutrino
**Step 1)** Create a new model and name it User (by convention Neutrino requires you to name your models as singular nouns)

![new model page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zysg2l1t2oszlj6vo78p.png)

&nbsp;
### Adding Data Parameters
**Step 2)** Click on 'authentication', which will automatically create the username, email, and password parameters, and manually pass in the _name:string_ and _bio:text_ params by clicking on 'ADD PARAM'

![add param to model](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nmntyv82won3z5v78783.png)

**Step 3)** Create the Post and Comment models and pass in their required data parameters that we specified before. 
So for Post, it would be _title:string_ and _content:text_, and for Comment, it would be _content:string_ and _user:string_. 
After doing this, your models page should look like this:

![models page with data params](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9li2sh8v5528kyp44qnx.png)

&nbsp;
### Implementing Model Relations
**Step 4)** We said we wanted two one-to-many relationships, one between User and Post, and one between Post and Comment.
- We can do this by passing a _has_many: Post_ param for User and a _belongs_to: User_ param for Post.

![add relationship to model](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5ireb5pfu60kr6vszcoc.png)

After doing this for Post and Comment, your models page should look like this:

![models page with relationships](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/brstjyeo04suz1iwqbte.png)

And well, the Relations page doesn't really do much yet, but if you did everything correctly, it should look like this:

![relations page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6z394w765ymfw705dede.png)

&nbsp;
### Routing
**Step 5)** We enabled authentication by defining User as an _authObject_ in the Models page, now we want to specify which routes we actually want and which ones we want to protect.
- Lets head to the Routes page, which should originally look like this:

![routes page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/suau6ygveqt4eny2yit9.png)

Neutrino scaffolds create all of the RESTful routes for each model by default, so for user it would be index, show, create, update, destroy, etc. 
- Note that the _new_, and _edit_ routes are created only in the frontend, they simply render a form and don't actually call the backend until you hit submit. 
(with the exception that _edit_ actually makes a GET request to load all the current model info).

&nbsp;
#### Disabling Unnecessary Routes
**Step 5a)** We clearly don't want each route available for every model, so let's start out by disabling a couple. 

- We don't really want users to be able to access a list of all comments ever created so lets disable the _index_ route for Comment
- We also don't need an individual page to display a singular comment so we can go ahead and disable the _show_ route for for Comment
- And finally, let's say we don't want Users to be able to modify a comment after commenting, so let's disable the _update_ route for Comment (note that this automatically disables the _edit_ route too).

Your Comment routes should now look like this:

![comment routes](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kvwsgd1mqygjhjjmnm7m.png)

&nbsp;
#### Route Protection
**Step 5b)** By enabling route protection, we are enabling two things:
- The _verifyJWT_ middleware in the backend, which will make sure the user is authenticated before enabling them access to the route.
- The _PrivateRoute_ component in the frontend, which will automatically redirect the user to the login page if they're not authenticated.

We can split all routes into two main categories: public routes, accessible to anyone regardless of whether or not they're signed in, and private routes, which should only be accessible to logged in users.

- We want users to be able to see all posts and be able to click on a post to see its comments even if they're not logged in, so we can leave both the Post _index_ and _show_ routes as public. 
- We also want unauthenticated users to be able to create a new user (by registering an account), so we can leave User _create_ as public too.
- However, we want Users to be authenticated to do anything else.
- Let's protect all other routes by clicking on the _protected_ lock icon.

Your routes should look like this:

![users routes protected](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fcjpw31q6aecrajflxzn.JPG)

![posts routes protected](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vr4xzrbyzw8ul1zj11ys.JPG)

![comments routes protected](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vk2igyk3l1rkb24call1.JPG)

&nbsp;
#### Route Logic
**Step 5c)** Neutrino has a pretty neat feature of offering route logic templates for certain routes, these can be anything from hiding certain parameters such as passwords on GET requests, to verifying to see if a user is trying to modify another user's content. 

Lets look at these route by route:
- User _show_: 
  * A GET request to User will return all of the user's parameters by default (the password will be hashed but we still don't need other users to see this).
  * Lets enable the _protect info_ logic template by clicking on the gear button and then on _protect info_ to automatically hide the password field for the logged in user and the password and email field for anyone else (even if a user is signed in, we don't want them to access another user's email).
  * You could also hide other parameters if you wanted, so if you didn't want other users to access the _name_ parameter, you could pass that into _hide_ as well.
  * Make sure to hit 'SAVE'.

![user show logic](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/httneuqos6jkcra9ux84.JPG)

- User _update_:
  * We clearly don't want users to edit other users' information so lets enable logic and click on the _protect update_ template.

![user update logic](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gczv5c515ohymw23vtlz.JPG)

- User _delete_:
  * We don't want users to be able to delete other users' accounts so lets enable logic and click on the _protect action_ template.

![user delete logic](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7ppkz7q9nqrdvd7n4goj.JPG)

Let's understand what we just did:
- **req.user.id**: Refers to the ID that is associated with the currently authenticated user making the request. This only works if VerifyJWT was enabled for this particular route.
- **data._id.toString()**: The data object is the object that we are trying to access from the database. We are then accessing the data's (which is of type User) _id parameter. Lastly we have to convert the _id object into a string, so we use toString().
- **hide**: Refers to a special shorthand that removes certain keys from the response object. In our example in the if statement we try to hide password and email, so on the user side when the response object is received the response will never contain the password, as it is sensitive information, and will only include the email if the user fetched is the same user making the request.
- **error**: Error is a special shorthand to send a 500 response to the user with the given Error message after the = sign. So if we wanted to send an error with a different message, “Not Nice”, we could replace the error line with error=Not Nice.

Now for Post:
- Post: _create_
  * When a user creates a new Post, we don't want them to be able to modify the id of the user that created it as this would essentially be impersonating another user. So let's enable route logic and click on the _protect create_ template.

![create post logic](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wlf7lkjejiqub10s69eh.png)
- Post: _update_
  * We obviously don't want users editing other users' posts.
  * We also don't want a user to be able to modify the user parameter for a post (even if it is their own) because this would essentially be impersonating another user. Let's enable route logic and click on the _protect update_ template.

![post update logic](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v9jyoi9yzts3jbru32nq.JPG)

- Post: _delete_
  * We don't want users to be able to delete another user's post, so lets pass in some route logic.

![post delete logic](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wz5yh8n6cfmvpbw20o9f.JPG)

Now for Comment
- Comment: _create_
  * Neutrino actually doesn't provide any templates for this route since we didn't specify a one to many with the _authObject_ (User), but we can use what we just learned about routes to do the same thing.
  * Since we don't want users to be able to make comments on behalf of another user.

```
if (req.user.id != req.body.user) {
    error=Incorrect parameters
}
```
![Comment create logic](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cdvzmdtcl7163hpsqsfx.png)

- Comment: _delete_
  * Since we don't want users to be able to delete other users' comments.

```
if (req.user.id != data.user) {
    error=Cannot delete another users comment
}
```
![comment delete logic](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0czkznppt0fqpv9vvzsu.png)

Lets understand what this is doing:

Remember that we're passing user:String as a parameter when creating a comment. That means that we're storing the id of the user that created the comment. As such, we can compare it with the id of the user making the request through _req.user.id_ to see if the user making the request is the same user that created the comment.

&nbsp;
#### Rewind
We still haven't discussed how we will implement likes. 
This is partially by design since I didn't want to intimidate beginners with too much information, but now you've made it this far so let's implement likes. 

- Likes will be a many-to-many relationship between User and Post (Even though we previously declared them to have a one-to-many relationship, they now have both).

- That is, a user can _like_ many posts, and a post can have likes from many users.

&nbsp;
### Implementing Likes
**Step 6)** Lets go back to the Models page and add another _has many: Post_ param for User and a _has many: User_ param for Post

Your Models page should look like this:
![new models page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0vmfz2nd559nf348i5pg.png)

Your Relations page should look like this:
![new relations page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/557962ppbgfvfnsh0pn5.png)

Note that Neutrino automatically adds two new routes for Many-to-Many relationships:

addPost and dropPost

![add and drop Post routes](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nk6bg2f4amswes50a1yi.JPG)

addUser and dropUser

![add and drop User routes](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6idb0a99w04l83922qp6.JPG)

These routes will be helpful since they automatically provide the logic to add a post to the user's _liked_ array and a user to a post's _liked_by_ array (we can change the name of the route methods later if we want).

&nbsp;
### Adding Mongo Connection String
**Step 7)** Go to the Settings page and add your MongoDB Connection string if you have it.
You could also do this later, you'll just have to insert it in the _index.js_ page of your server before you can run your application.

For help accessing your MongoDB Atlas Connection string, follow [this guide](https://docs.mongodb.com/guides/cloud/connectionstring/)

&nbsp;
### Saving Your Projects
Neutrino lets you create an account to save your projects which may be helpful in debugging or adding new features as your project grows. However, this is fully optional.

&nbsp;
### Export
**Step 8)** Click on the EXPORT button on the sidebar and add a project name and your email and you're done!
If you followed along correctly, Neutrino should download a zip folder containing all of your project's code

## You're Done (almost)!

&nbsp;
### Running Your Code
Extract the zip folder and open it up in your editor. Run the following commands in this order.
`cd server`
`npm i`
`node index.js`
**note:** If you haven't added a Mongo Connection String, you'll get the following error:
```
throw new MongoParseError('Invalid scheme, expected connection string to start with "mongodb://" or "mongodb+srv://"');
```

On a new terminal, run:
`cd client`
`npm i`
`npm run start`

If everything went correctly, you should see the following page:

![welcome page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ifftycb7ks06e0zpcqtf.JPG)

Nothing quite too interesting yet, but you can see that you can register a new user then log in with the specified username and password.
You can also try to create a new post and comment (if you try to pass in anything other than your own user's id for the _user_ parameter when creating a new comment you should get an error).

However, the whole frontend is pretty generic and we'll get around to fixing it. Let's fix up a couple of things in the backend first though.

&nbsp;
### Model Files

&nbsp;
#### User Model
##### /server/models/User.js

Your code should look like this:

```
const mongoose = require('mongoose');

const UserSchema = new mongoose.Schema({
	username: {
		type: String,
		required: true
	},
	email: {
		type: String,
		required: true
	},
	password: {
		type: String,
		required: true
	},
	name: {
		type: String,
		required: true
	},
	bio: {
		type: String,
		required: true
	},
    likes: [
		{
			type: mongoose.Schema.Types.ObjectId,
			ref: 'Post'
		}
	]
})

UserSchema.virtual('posts', {
        ref: 'Post',
        localField: '_id',
        foreignField: 'user'
});

UserSchema.set('toObject', { virtuals: true });
UserSchema.set('toJSON', { virtuals: true });

const User = mongoose.model('User', UserSchema);
module.exports = User;
```

Each object in the schema represents a parameter for the object, _likes_ represents the Many-to-Many association we created with Posts, which is simply an array of Object IDs for different posts.

The latter code in _UserSchema.virtual_ specifies our One-to-Many relationship with Post. 
Mongoose virtuals allow us to fetch the posts associated with the given user without actually storing them in the User document in the database, which will help performance. 

You can read more about Mongoose virtuals [here](https://mongoosejs.com/docs/tutorials/virtuals.html)

&nbsp;
#### Post Model
##### /server/models/Post.js

Your code should look like this:

```
const mongoose = require('mongoose');

const PostSchema = new mongoose.Schema({
	title: {
		type: String,
		required: true
	},
	content: {
		type: String,
		required: true
	},
	user: {
		type: mongoose.Schema.Types.ObjectId,
		ref: 'User',
		required: true
	},
	liked_by: [
		{
			type: mongoose.Schema.Types.ObjectId,
			ref: 'User'
		}
	]
})

PostSchema.virtual('comments', {
	ref: 'Comment',
	localField: '_id',
	foreignField: 'post'
});

PostSchema.set('toObject', { virtuals: true });
PostSchema.set('toJSON', { virtuals: true });

const Post = mongoose.model('Post', PostSchema);
module.exports = Post;

```

&nbsp;
#### User Controller
##### /server/controllers/UserController.js

Neutrino sometimes messes up the route methods whenever you have different relationships between the same two models (remember how we had both a One-to-Many and a Many-to-Many between User and Post), so make sure your User Controller has these two methods:

```
addPost: async (req, res) => {
    const { user_id, post_id } = req.params;
    UserModel.findByIdAndUpdate(
      user_id, 
      { $push: { likes: post_id } },
      (err, data) => {
        if (err) {
          res.status(500).send(err);
          console.log(err);
        } else {
          res.status(200).send(data);
          console.log('Post added!');
        }
      }
    )
  },

  dropPost: async (req, res) => {
    const { user_id, post_id } = req.params;
    UserModel.findByIdAndUpdate(
      user_id, 
      { $pull: { likes: post_id } },
      (err, data) => {
        if (err) {
          res.status(500).send(err);
          console.log(err);
        } else {
          res.status(200).send(data);
          console.log('Post dropped!');
        }
      }
    )
  },
```

Let's also fix up the _.populate()_ function in **find()** as Neutrino may have written a slight bug.

- First, we need to populate posts since Mongoose virtuals only gives us the ids of the posts belonging to the given user. 
The _populate_ function replaces this id with an object containing the actual posts' information, specifically the parameters defined in _select_

- We also need to populate _likes_ with the objects corresponding to actual post data

You can read more about Mongoose's _populate_ function [here](https://mongoosejs.com/docs/populate.html)

Your _find_ function should look as follows:

```
find: async (req, res) => {
    const { id } = req.params;
    try {
      const data = await UserModel.findById(id)
				.populate({ path: 'posts', select: 'title' })
        .populate({ path: 'likes', select: 'title content' })
			if (req.user.id != data._id.toString()) {
			  data.password = undefined;
			  data.email = undefined;
			} else {
			  data.password = undefined;
			}
      res.status(200).send(data);
    } catch (err) {
      res.status(400).send(err.message);
      console.log(err);
    }
  },
```

&nbsp;
#### Post Controller
##### /server/controllers/PostController.js

Lets rename some variables in the addUser and dropUser methods.
In _$push_ and _$pull_, rename _users_ to _liked_by_

```
addUser: async (req, res) => {
    const { post_id, user_id } = req.params;
    PostModel.findByIdAndUpdate(
      post_id, 
      { $push: { liked_by: user_id } },
      (err, data) => {
        if (err) {
          res.status(500).send(err);
          console.log(err);
        } else {
          res.status(200).send(data);
          console.log('User added!');
        }
      }
    )
  },

  dropUser: async (req, res) => {
    const { post_id, user_id } = req.params;
    PostModel.findByIdAndUpdate(
      post_id, 
      { $pull: { liked_by: user_id } },
      (err, data) => {
        if (err) {
          res.status(500).send(err);
          console.log(err);
        } else {
          res.status(200).send(data);
          console.log('User dropped!');
        }
      }
    )
  },
```

**note:** Since we renamed the _users_ array to _liked_by_ in the Post model, we'll run into a few errors if we don't also change the naming in PostController.

Make sure _find()_ and _index()_ look like this:

```
find: async (req, res) => {
    const { id } = req.params;
    try {
      const data = await PostModel.findById(id)
				.populate({ path: 'comments', select: 'content user' })
				.populate({ path: 'liked_by', select: 'username name' })
			
      res.status(200).send(data);
    } catch (err) {
      res.status(400).send(err.message);
      console.log(err);
    }
  },

  all: async (req, res) => {
    try {
      const data = await PostModel.find()
				.populate({ path: 'comments', select: 'content user' })
				.populate({ path: 'liked_by', select: 'username name' })
			
      res.status(200).send(data);
    } catch (err) {
      res.status(400).send(err.message);
      console.log(err);
    }
  }, 
```

&nbsp;
### Server Index

The server index page defines all of our RESTful routes and pointes them to the appropriate controller method.

It also includes _verifyJWT_, a middleware function that checks for a valid JWT token to ensure the user is authenticated.

Including _verifyJWT_ in a route will require the user to be authenticated before calling the controller function.

##### /server/index.js

Make sure to include _verifyJWT_ for the following routes:
- addPost
- dropPost
- addUser
- dropUser

Your code should look like this:

```
const express = require('express');
const mongoose = require('mongoose');
const cors = require('cors');
const jwt = require("jsonwebtoken")

const app = express();
const PORT = 8080;
const corsOptions = {
  origin: "*"
}

app.use( express.json() );
app.use( cors(corsOptions) );

mongoose.connect('<YOUR OWN CONNECT STRING HERE>', {
	useNewUrlParser: true,
});


function verifyJWT(req, res, next) {
  if (!req.headers["authorization"]) {
    return res.status(400).json({ message:"No Token Given", isLoggedIn: false });
  }

  const token = req.headers["authorization"].split(' ')[1];
  if (token) {
    jwt.verify(token, "pleasechange", (err, decoded) => {
      if (err) return res.status(500).json({ message: "Failure to Auth", isLoggedIn: false });
      req.user = {};
      req.user.id = decoded.id;
      req.user.username = decoded.username;
      next();
    })
  } else {
    return res.status(400).json({ message: "Incorrect Token Given", isLoggedIn: false });
  }
}


// CONTROLLERS
const UserController = require('./controllers/UserController');
const PostController = require('./controllers/PostController');
const CommentController = require('./controllers/CommentController');


// ROUTES
app.get('/users', verifyJWT, UserController.all);
app.get('/users/:id', verifyJWT, UserController.find);
app.post('/users', UserController.register);
app.put('/users/:id/edit', verifyJWT, UserController.update);
app.delete('/users/:id', verifyJWT, UserController.delete);
app.post('/users/:user_id/add-post/:post_id', verifyJWT, UserController.addPost);
app.post('/users/:user_id/drop-post/:post_id', verifyJWT, UserController.dropPost);

app.get('/posts', PostController.all);
app.get('/posts/:id', PostController.find);
app.post('/posts', verifyJWT, PostController.create);
app.put('/posts/:id/edit', verifyJWT, PostController.update);
app.delete('/posts/:id', verifyJWT, PostController.delete);
app.post('/posts/:post_id/add-user/:user_id', verifyJWT, PostController.addUser);
app.post('/posts/:post_id/drop-user/:user_id', verifyJWT, PostController.dropUser);

app.post('/comments', verifyJWT, CommentController.create);
app.delete('/comments/:id', verifyJWT, CommentController.delete);

// AUTH
app.post('/login', UserController.login);
app.post('/register', UserController.register);

app.listen(
	PORT,
	console.log("Server running on port 8080...")
);
```

&nbsp;
### Fixing Up the Front End

Each Model comes with 4 pages built in corresponding to each of the CRUD functions
- **[ModelA]s.js** : an index page containing a list of all [ModelA]s created
- **[ModelA]Show.js** : a page displaying all information corresponding to a single [ModelA]
- **[ModelA]Edit.js** : a page rendering a form to update a specific [ModelA]
- **[ModelA]New.js** : a page rendering a form to create a new [ModelA]

&nbsp;
#### Display User Page
##### /client/src/Pages/User/UserShow

UserShow.js renders a pretty generic page, lets change a few things to make it look more like a profile page.

##### Displaying Params

You can change the header to greet the user with their username rather than id, also, since we added logic for hiding the user's _email_ and _password_, you can delete the _password_ parameter and add a conditional to only render _email_ if its not null.

##### Conditional Rendering

As for the _EDIT_ and _DELETE_ buttons, we only want to display them if the currently authenticated user is the same user we're displaying. 

To do so, first import _useContext_ from react and include the following lines:

```
import { UserContext } from '../../hooks/UserContext';

...
export default function UserShow(props) {
  const { authUser } = useContext(UserContext);
```

Now, we can access the signed in user if it exists by simply calling _authUser_

Wrap both buttons with the following conditional:

```
{ authUser._id === id && 
          <div>
            <Button variant="outlined" style={{marginRight: 15}}
              onClick={() => navigate(`/users/${id}/edit`)}>edit
            </Button>
            <Button variant="contained" color="error" 
              onClick={handleDelete}>delete
            </Button>
          </div>
}
```

##### Displaying Liked Posts

We can display liked posts by simply calling the _user.likes_ array.

It might look something like this:

```
<div className='displayContainer'>
	<h3>Liked Posts</h3>
	<ul>
	{user.likes && user.likes.map((post, i) => (
		<div className='listItem' key={i}>
			<li>{post.title}</li>
			<Button variant='outlined' size='small'
        onClick={() => navigate(`/posts/${post._id}`)}>show</Button>
		</div>
	))}
	</ul>
</div>
```

&nbsp;
#### Display Post Page
##### /client/src/Pages/Post/PostShow

Again, this page is currently pretty generic, but we can fix it up a bit by changing the header and how we display some of the params.

What's a bit more interesting though is how we're dealing with likes.

##### Liking Posts

Change the _addUser_ and _dropUser_ functions to the following:

```
function likePost() {
    try {
      axios.post(`http://localhost:8080/posts/${id}/add-user/${authUser && authUser._id}`,
				{}, { headers: authHeader() });
      axios.post(`http://localhost:8080/users/${authUser && authUser._id}/add-post/${id}`,
				{}, { headers: authHeader() });
    } catch (e) {
      console.log(e);
    };
    window.location.reload();
  }

  function unlikePost(droppedId) {
    try {
      axios.post(`http://localhost:8080/posts/${id}/drop-user/${authUser && authUser._id}`,
				{}, { headers: authHeader() });
      axios.post(`http://localhost:8080/users/${authUser && authUser._id}/drop-post/${id}`,
				{}, { headers: authHeader() });
    } catch (e) {
      console.log(e);
    };
    window.location.reload();
  }
```

All we're doing is changing the name of the function for readability and changing the user id to the id of the currently authenticated user (This will require you to import _useContext_ _UserContext_ define _authUser_ like we did in UserShow).

##### Conditional Rendering 

If we only want to display the edit and delete buttons if the post belongs to the authenticated user, wrap the buttons in the following conditional:

```
{ post.user === authUser._id &&
          <div>
            <Button variant="outlined" style={{marginRight: 15}}
              onClick={() => navigate(`/posts/${id}/edit`)}>edit
            </Button>
            <Button variant="contained" color="error" 
              onClick={handleDelete}>delete
            </Button>
          </div>
          }
```

##### Displaying Like/Unlike Button

This button will render depending on whether or not the currently authenticated user has already liked the post.

Therefore, we can create two new buttons for liking and unliking and wrap them in the following ternary opertor:

```
{ (post.liked_by && post.liked_by.some(user => user._id === authUser._id)) ?
          <Button variant="contained" color="error" 
            onClick={unlikePost}>unlike
          </Button>
          :
          <Button variant="contained" color="success" 
            onClick={likePost}>like
          </Button>
        }
```

Lets understand what this is doing:

- post.liked_by is the array of users that have liked this post
- _.some((user) => condition)_ returns true if any user matches the following condition
  - In this case, we want to return true if the currently authenticated user has liked the post, that is, if _authUser_ is included in the posts _liked_by_ array
  - If true, we want to display the _unlike_ button, otherwise, display the _like_ button


&nbsp;
## Finishing Thoughts

Okay there's a chance after reading everything and making the slight changes this project took a little over 30 minutes.
But really, we had the bulk of our functionality up and running in only a couple of minutes due to Neutrino.

There's obviously a lot more that can be done fixing up the frontend and customizing it to look more like an actual blog app, but hopefully after following these examples with _UserShow_ and _PostShow_, you gathered enough on your own to get started with the rest.

Happy Coding!