MERN Stack — The Old Facebook Comment Box Tutorial except 2018

Unrelated, but elegant AF

Ok, here we go! This is a (much needed) update to my original MERN Facebook Comment box tutorial. That can be found here, but this is the new and improved version. I’ve 1) learned A TON since that last tutorial and, 2) the community has come to some best practices that we will implement here. The premise will be the same — we will be building a Facebook-like comment box.

As before — this tutorial expects a basic understanding of React — this is a MERN tutorial, not a React tutorial. If you are not comfortable with React, I would highly recommend their excellent docs.

Before we start, the code is here. I will hopefully make this into a Code Sandbox playpen also eventually, but if anyone wants to take the initiative, I would love that!

Also, I tend to speak fairly colloquially and loosely. This means I use phrases like “just do this”, and such that may make it seem like it is very easy. If you feel like you just aren’t getting it, remember, this stuff is pretty tricky. Everyone struggled through it in the beginning (and still does!). Feel free to reach out with questions or comments on problems you are having or how I can make this more helpful and usable.

Anyways, lets get started!

Some Changes

I will be using Yarn instead of NPM for this project. You can feel free to use whichever you prefer. if you want to use npm, note that yarn add would become npm i[nstall]. In most other cases, you can just replace yarn with npm and will be fine. If you want to give yarn a try, you can find it here .

Initial folder structure

First things first, we need to make a place for all our code to live. To create a new folder and move us into it, lets enter into the terminal:

$ mkdir mern-comment-box && cd mern-comment-box

We will be using the npm package create-react-app for our base React code. This is an AMAZING package to get started, so if you dont have it, run:

$ npm i -g create-react-app

Now, we can run $ create-react-app client

to bootstrap our react front end! Neat!

Our project structure is going to look something like this:

Since create-react-app has set our whole client folder up for us already, lets start on the backend.

$ mkdir backend && mkdir backend/models && touch backend/models/comment.js && touch backend/server.js

Before we go any further, we should run $ yarn init to create our package.json folder and be ready to start imports. We will also be using github to track our progress because it is good practice, so you should start doing it (it only takes losing good progress once to realize its true value)

so we can run $ git init to create our git repository.

Now, lets create a file which will tell git which files to ignore (our node_modules/ specifically).

$ touch .gitignore will create this file. Open it up and enter:

.gitignore
node_modules/

this tells git to ignore: 1) this file, and 2) all node_modules/ folders

Now, lets commit our progress. We should be doing this periodically. We can do this easily by:

git add .
git commit -m ‘Initializing repository [or whatever you are doing in this set of work]

Side note on git:

Lets see the importance of using git tracking really quickly.

Now that we have a repository with commits in it, if we do something wild like say…

$ rm -rf backend

Oh shoot! We accidentally just deleted all our work! (even though its just an empty folder). If you’ve ever felt this pain or panic before, lets let our savior git come and rescue us from this hellscape.

Lets do some exploring:

$ ls confirms our worst fears, we accidentally removed our _ENTIRE_ backend!

If we run $ git log — oneline, we can see our commit history. Mine looks like this (yours may be different)

de6a88a (HEAD -> master) Initializing repository

If we run git reset — hard we will reset all our changes to our last commit (which is called HEAD), `git`ing all our work back :)

**Note: the — hard flag will throw away all your uncommited changes

What if we accidentally commited the removal of our backend?

$ rm -rf backend && git add . && git commit -m ‘removing backend for git example’

$ git log — oneline shows us where our HEAD is (our last commit), as well as our first commit:

da36490 (HEAD -> master) removing backend for git example
de6a88a Initializing repository

Now running $ git reset — hard doesn’t help us. It still brings us to where we removed our backend folder.

Thankfully, we can use $ git reset — hard HEAD~1 (1 further back from HEAD) or the commit hash $ git reset — hard de6a88a to get our work back.

So with this knowledge, use git tracking to save meaningful changes. To learn more about the power of `git`, give this great lesson try!

Back from the git sidequest

Okay! We’re now in a good place to start! We’re going to be doing a bunch of work in the backend first, so setting up our server.js file, we can run $ yarn add express body-parser nodemon morgan mongoose concurrently babel-cli babel-preset-es2015 babel-preset-stage-0 to get our dependencies in order.

- Express: is what we will be using as our server framework (The E in M __E__ RN)

- Body parser: a package which helps us get the body off of network requests

- Nodemon: a package that watches our server for changes, and restarts it (for a better dev experience)

- Morgan: a logging package to make it easier to debug our network requests to this api

- Mongoose: a package that lets us interact with MongoDB in an easier way (the M in __M__ ERN)

Getting our components set up

Lets do some clean-up in our client folder. First, we can $ cd client for easier terminal access to files and folders we are currently using. (Protip: hitting tab as you are typing folders/files in the terminal will auto-complete for you if it can figure it out) We can $ rm src/logo.svg src/App.css src/App.test.js src/App.js, as we dont need these. Now, lets make all the components we need. In a larger app, i would break these out into Container and Component folders, but since we are only dealing with a few files, we can keep them all under src/. Lets $ cd src && touch CommentList.js CommentBox.js CommentForm.js Comment.js data.js CommentBox.css to make all the files we need. The data file will contain hardcoded data for intial testing, but by the end we will remove it, as all our data will live in our database.

In index.js, we need to make some changes so it points to CommentBox instead of App, so change your file to use CommentBox instead of App:

We need the react-markdown package to convert markdown to text, and we need something to fetch data from the browser. For this, we will use the whatwg-fetch package, which is a polyfill for the window.fetch object. We will also bring in the prop-types package to use to ensure we are getting the expected type in to our component.So we can $ yarn add react-markdown whatwg-fetch prop-types.

We will also be using es-lint to catch errors some easy problems, and defining our prop types to check that the data we are getting passed in is what we expect.

$ yarn add --dev eslint babel-eslint will get us the prop types, eslint, and eslint babel parser package. I use the airbnb eslint rules, and we can go to the npmjs page and see a command to get it with all its depedencies. Since we are using yarn instead of npm, we will modify it slighly, replacing the npm install — save-dev` with `$ yarn add —-dev, like so:

$ (
export PKG=eslint-config-airbnb;
npm info "$PKG@latest" peerDependencies --json | command sed 's/[\{\},]//g ; s/: /@/g' | xargs yarn add --dev "$PKG@latest"
)

Note: This works with UNIX and iOS, see the eslint airbnb page for Windows). Also, this is a terminal command. Copy and pasting this into your command line will automatically download the required dependencies for the airbnb es-lint package.

We should now add an .eslintrc.json file which will set up and modify the rules we want to use. Making sure you are under the client/ folder, run $ touch .eslintrc.json. In that file, add:

Feel free to change these rules as you see fit, as long as you are using a consistent style that works for you.

These are how the files will statically look, and going forward we will replace the hardcoded values with dynamically generated things from the server.

At this point, you may notice some linting errors. They are ok for now, as they will be resolved through our development. If we fire up our React app with $ yarn start:client , we should see something like this:

Wow.

Getting the server set up.

Lets get our mern-comment-box/package.json file set up so we can start our app up. Under “scripts”:, add the following three lines:

this creates 3 commands we can now run. $ yarn start:server will only start our backend server, $ yarn start:client will start our react app, and $yarn start:dev will start them both simultaneously (thanks to the concurrently package).

The babel-cli, babel-preset-stage-0 and babel-preset-es2105 packages allow us to use new javascript syntax in our server files. You can choose to not use these, but note that things like the `import` lines wont work, for example. You can opt to use const express = require(‘express’) in that case. We may not use the stage-0 features, but I like to have them availble to play with.

In our mern-comment-box/backend/server.js file, lets pull in all the packages we need and get a skeleton server set up.

Running $ yarn start:server, we will see a message saying “Listening on port 3001” or whichever port you set your variable up for.

Navigating to http://localhost:3001/api/, you should see:

Hello right back atcha, world ;)

And in your terminal, we can see what the morgan package is doing for us:

^^^ we have a GET request!

Great! Now the real fun begins!


Going forward, i will be using Postman to test our server, as it is easier to send POST requests to than using the command line `curl` function, but if you are more comfortable with that, feel free to use curl, you wacky hax0rz ;)

Integrating the database

For this part, I will be using MLab which you can find here. It is a database-as-a-service provider for MongoDB. Make a free account, then log in. Click on the Users tab, and click add database user. Once you have a username and password, we can integrate it into our server.js file.

On your MLab page, you should see something at the top that looks like this:

M-Lab stuff

We will use the connect “using a driver via the standard MongoDB URI” option. Copy that link, and we will create a secrets file to put it in.

Now, we should $ touch backend/secrets.js. I will not be doing this (for the sake of you being able to see the file), but be sure to put your secrets.js file in your .gitignore folder! Or, you can use environment variables and reference them that way — which is what i will be doing.

To set things as environment variables, you can add them to your .bash_profile as exports, or do it via the command line. Since this is out of scope of this tutorial, i will just say if you want to use environment variables, you can type $ export DB_URI=mongodb://<dbuser>:<dbpassword>@ds161529.mlab.com:61529/mern-comment-box (replacing <dbuser> and <dbpassword> with the username and password you created through mlab). To read more about using environment variables, see this great article by Glynn Bird. If you prefer, you can replace process.env.DB_URI in the secrets.js folder below with it, but be sure not to commit it ot your repo!

(Thanks to Github users ssanaul and pjcevans for the PR’s with the secrets feature and fixing the mongoose warning!)

Now, we can connect our database in our backend/server.js:

Next we will need to create the Schema that will show what our database entries look like.

Now back in our backend/server.js file we import that with our dependencies:

Getting and posting to the database

In our backend/server.js file, we can now create a new route and give it GET and POST HTTP methods to retrieve data from and post data to our database we connected. Add this in below our root route:

Note: If you are not familiar with object destructuring, const { author, text } = req.body; may seem a little strange. This is basically just pulling those fields off the req.body object and creating two variables (author and text) from those values.

Note that after you save, nodemon will automatically restart your server, so changes should be instantaneous. Now if we use Postman to check out our route we just created, we can do a GET request to localhost:3001/api/comments and we see…. Nothing!

This is because our database is empty! Lets test out our brand new POST method we created and add our first comment! If we send a POST request to the same route /api/comments and put our author and text in, we should see our success message.

Back to React!

Now that were back on the front end, lets get our data from our brand new server! First, in our client/package.json file, lets add a proxy. This basically just tells our react app to try to use this other url if it cannot resolve the endpoint through its own. Now, instead of fetching from ‘http://localhost:3001/api/comments/', we can just use ‘/api/comments/’

Now, were going to set a poller to fetch data from the server every 2 seconds (2000 miliseconds). This is just going to hit our /api/comments endpoint and return an array of data. We will also add a bit of state to check for errors. If we receive an error, we can print it to screen to show the user.

When we start our server, we should see the comment that we posted earlier via Postman! We are also adding an error state, and a place to show errors if any come through, and setting the author and text to bits of state that we pass into CommentForm.js.

Git sidequest

If you are following along using git, once this is working correctly it would be a great place to commit!)

$ git status // … lots of uncommited changes in red
$ git add . // add all your changes (replace . with specific files if you want to break work in separate commits)
$ git commit -m 'Creating get/post route for comments endpoint and creating fetching live data from server in CommentBox'

Front End: Posting a new comment

Now we should hook up our inputs so we can post new comments through the site, not just through Postman. In CommentBox.js:

There are a few interesting things going on here. We’re using a promise to post to our server. If you’re not familiar with promises, this is pretty much just an async function. It will start and finish at some later time. The .then fires and converts the response to json, the following .then takes that json and does what we want with it, either showing an error or clearing out our inputs. We are also object destructuring again to get the author and text variables out. This just makes the body easier to read for ourselves.

Now when you look at your react app, the new comment we added to the database via Postman is there! If you look in your developer console, we have a warning saying ‘Each child in an array or iterator should have a unique “key” prop…’. MongoDB adds an ID tag to each post labeled “_id”. We can change our CommentList component to use it like so:

Optimizing Comments

We can finish up the (old, now defunct, but still interesting) Facebook tutorial now with a bit of optimization to our CommentBox.js component.

Here, we are creating a new variable called data. This data is made up of our array of state (using the spread operator), which takes each element out of the state array and puts it in the new array. After, we append the new comment to the end, and set this variable to our state.

Putting the UD in CRUD

Lets hop back in to our backend/server.js file one more time to add the PUT(Update) and DELETE(…Delete) routes to our API. We will need to direct them to a specific post, so we can use the :params route to specify which comment we are referring to.

Now if we go back in to Postman, we can send either a DELETE or PUT request with the “_id” at the end of the url. Sending a DELETE request will remove it, and sending a PUT request with a different author or text will update the comment.

If we go back into CommentBox.js, we can add a couple new methods that will handle our update and delete logic. All we need to do is call fetch again, this time passing in the id of the comment we want to either update or delete. We can also refactor the submit comment a bit to handle both sending updates of existing comments as well as new comments. We can add a new bit of state, updateId, which can either be null or an id. If it is an id, that is the comment which will be updated. If it is null, a new comment will be posted. Not exactly bulletproof logic, but good enough for this example.

Now, we just need to pass those methods into Comment.js through CommentList.js:

and Comment.js becomes:

Note: we are adding links in to comment as well to update and delete each comment.

Finally, for the finishing touch, we can add the moment package. Making sure we are in the client/ directory, $ yarn add moment. Now, importing that into Comment.js and using it like so right above the two <a> tags will give us a nicely formatted relative time of the post:

Swooooooooon ❤❤❤

There you have it! Hopefully that’s enough to get you started! Please let me know if you have any issues, comments, or anything. Im on twitter at @spacebrayn

Also, if there is any way you think of to help improve this tutorial, please make a pull request on the Github repo! I’d love contributors!

❤ bryan