Learn How to Build a Full-Stack Application Using Node.Js and React in 2021 — Part One (Back end)

Eli Sultanov
14 min readJan 25, 2021
full stack application using node and express
Photo by Nate Grant on Unsplash

We will be building a crud application. Crud stands for create, read, update, and delete, and every website/app that we use on daily basis relies on this concept.

Hopefully, this tutorial will help you guys understand how to build a full stack application using Node.js and React while also understanding how to structure your files/folders.

STEP 1: Intro

The first thing we will need to do is create a new folder, doesn’t matter where just make sure it is accessible. I’ll create a folder named crudApp.

STEP 2: Splitting our terminal into two mini windows.

Open VSCode and make sure you have two terminals open inside VSCode, the image below will give you an idea of what I mean.

To have two terminals open at the same time on a Mac, hit CMD+ \ on windows I believe it’s the Windows key + \(correct me if I am wrong).

STEP 3: Creating our client & server folder

On the left terminal type mkdir server. The following command will create a new folder called server.

On the right terminal type in npx create-react-app client. This command will create a react app called client.

STEP 4: Installing the required packages

Now that we have some of our basics done, let’s start installing some packages that will help us build this awesome full stack app.

On the left side, the one with our server folder, type npm init -y. This will initialize our folder with package.json. -y is simply a flag that tells npm to answer yes to every question the initializer asks.

side note: -y flag isn’t required, I use it because it makes my life slightly easier.

It’s time to install some packages. The packages that we will install are express.js (Node.js framework that helps us build servers with minimal code), cors (stands for cross-origin requests, and the job of this package is to help our client communicate with our backend), mongoose (Mongoose is an ODM (Object Document Model) which helps us set up MongoDB (our database) to store our information), and nodemon (which will help us keep our server up and running while we make live changes and restart the application automatically for us).

To install the listed packages run: npm i express cors mongoose nodemon

This will should take a few seconds and make sure we have everything installed, head over to package.json and make sure you have all of the packages installed.

Your package.json should look something like this:

side note: By default your package.json won’t have “type”: “module” make sure to add it because it will help you write more modern JS code (ES6).

On the right side, the one with our client we can start installing packages without first running npm init because npx create-react-app already created a package.json for us.

In the client folder we will install axios (axios helps us with sending requests to server in a much easier fashion), redux (Redux is a state management tool), react-redux (This little nugget binds react and redux together to create a magical unicorn), redux-thunk (There seems to be a lot of confusion with this one, but from what I understand it helps us deal with async functions when working with redux), and lastly, say hello to react-hook-form (A lot of people complain that handling forms in react is a difficult task so to solve this problem we will use react-hook-form and save your tears for some Netflix show).

To install all of the listed packages we will run: npm i axios redux react-redux redux-thunk react-hook-form

The client package.json should resemble something like this:

STEP 5: Organizing our server folders

Let’s set up the file/folder structure for our server. Your server should contain the following folders: db, controllers, models, routes

Inside the db folder create a file called db.js.

Inside the controllers folder create a file called posts.js.

Inside the models folder create a file called post.js.

Inside the routes folder create a file called posts.js.

We will set up the file structure for our client folder in a little while.

STEP 6: Setting up our database

Before we start coding let’s set up our database, MongoDB.

Head over to The most popular database for modern apps | MongoDB and create an account.

On the next screen name your organization and project, select your preferred coding language and go to the next page.

Select the shared cluster

For our database we will use AWS and select the closes server location to your home.

Once this is all done, we should have our cluster set up. This will take some time (5 minutes give or take).

While our cluster is starting up, head over to the Database Access tab and create a new user.

Select password as your Authentication Method and add your user name and password, make sure to write them down somewhere since they will be crucial as we start coding.

Once you have done setting up the user, head to the Network Access tab and click Add IP Address. Select the option “ADD CURRENT IP ADDRESS”.

At this point our cluster is up and running, so head back to the Clusters tab and click connect.

A window will popup. Select the option that says Connect your application.

At this point simply copy the URI and we should be done with the set up process.

Your URI should look something like this:

mongodb+srv://123:<password>@cluster0.sfi3i.mongodb.net/<dbname>?retryWrites=true&w=majority

side note: Make sure to change <password> with the password you created when you created your user. Also, change <dbname> with the name you want your collection to have.

STEP 7: Connecting to our database

Head back to VSCode and let’s start coding.

Navigate to the db folder that we created earlier and open the db.js file.

Inside the file, add the following lines.

import mongoose from 'mongoose';mongoose.connect('uri that you got from MongoDB goes here', { useNewUrlParser: true, useCreateIndex: true });

The first line imports mongoose for us. The second line connects to mongoose.

mongoose.connect accepts three parameters. The first parameter will be our uri, the second parameter will be an object that accepts options such as useNewUrlParser and useCreateIndex, and the third parameter is a callback function.

Side note: To learn more about what useNewUrlParser & useCreateIndex do checkout the following stackoverflow answer.

At this point, our DB is connected to our application.

Photo by Alan Hurt Jr. on Unsplash

STEP 8.5: Let me clear some future confusion

Before I jump any further I need to explain a few things here so that no one ends up confused.

If you ever worked with a database before, you most likely worked with what is known as a relational database, think of MySQL or PostgreSQL.

In a relational database, your data can form relationships, like for example, one customer can purchase many items, this is known as one-to-many relationships where many purchases are associated with a single customer.

Now, MongoDB does things completely differently. MongoDB is a non-relational database which means data isn’t related to each other.

In a typical relational database, we have the database, tables, columns, and rows, where data is stored into tables that are then organized into columns and displayed as rows.

In MongoDB things get interesting because we don’t have tables, columns or rows, instead what we have are databases, collections, and documents.

side note: To learn more about the difference between relational databases and non-relational databases, check out this guide that was written by Mark Rethana.

STEP 9: Creating our Model

Head to the models folders, located inside the server folder and open the post.js file.

Add the following code:

import mongoose from 'mongoose';const postSchema = new mongoose.Schema({"title": String,"content": String,"author": String,"tags": [String],}, { timestamps: true });const Post = mongoose.model('post', postSchema);export default Post;

The code above tells mongoose to create a new collection called posts (post will be pluralized ), and this collection will have documents and each document will contain an _id (we never create an id ourselves, mongoose will take of that for us), title, content, author, tags, and timestamps. Lastly we are exporting our schema so that we can use it throughout our application.

STEP 10: Creating our controllers.

Controllers help us maintain a cleaner code base where each task is placed into its own function.

Head to the controllers folder that you’ve created inside the server folder and open the posts.js file.

The first thing you need to do is import our model

import Post from '../models/post.js';

The code above imports our model so that we can work with it.

The first operation you want to make is the ‘create’ part in crud.

Add the following code

export const createPost = async (req, res) => { try {  const post = new Post(req.body);  await post.save();  res.status(201).json(post); } catch (error) {  res.status(400).json({ success: false, error }); }}

The create function tells our model to go and create a new document, the document will have everything that req.body sends to it.

Once we added a new document to our database, now we need to save it by calling:

await post.save();

side note: This is an asynchronies function which means we need to use the await keyword. To learn more about async/await function checkout this post.

res.status(201).json(post);

The line of code above tells express (Node.js framework that we installed earlier) to respond with a status code of 201, which means creation was successful, and send the response in a JSON format, containing our newly created post.

side note: To learn more about status codes, check out this post.

The next function we want to create is the ‘read’ part of crud.

export const getPosts = async (req, res) => { try {  const posts = await Post.find();  res.status(200).json(posts); } catch (error) {  res.status(400).json({ success: false, error }); }}

The code above tells mongoose to go and retrieve everything from the database and send it to us in a JSON format with a status code of 200.

The update part of crud will consist of the following lines of code:

export const updatePost = async (req, res) => { const allowedOptions = ['title', 'content', 'tags', 'author']; const selectedOption = Object.keys(req.body); const doesExists = selectedOption.every(option =>    allowedOptions.includes(option));if (!doesExists) { return res.status(404).json({ success: false, error });}try {  const post = await Post.findById({ _id: req.params.id });  selectedOption.forEach(option => post[option] = req.body[option]);  await post.save()  res.status(200).json(post); } catch (error) {  res.status(404).json({ success: false, error });  }}

This looks scary I know, let’s try to digest this beast together.

const allowedOptions = ['title', 'content', 'tags', 'author'];

The line above is simply an array of keys that we created inside our model.

const selectedOption = Object.key(req.body);

The next line converts the req.body into an array. Since req.body consists of key:value pairs, Object.key extracts the key part and saves into an array, in our case this array is called selectedOption.

const doesExists = selectedOption.every(option =>    allowedOptions.includes(option));

Here we are looping through the selectedOption and checking to make sure that the keys that we got from req.body exist inside our allowedOptions array.

if (!doesExists) { res.status(404).json({ success: false, error });}

This is a simple if statement that will be triggered only if the keys that we got from req.body don’t match with what we have inside the allowedOptions.

const post = await Post.findById({ _id: req.params.id });

The line above is asking our Post collection to check whether an id that we got from req.params.id (when we start working on the routes folder, this will make more sense) exists inside our collection.

selectedOption.forEach(option => post[option] = req.body[option]);res.status(200).json(post);

In the last two lines, we will loop through what we got from req.body and change the values of our post to the new values. If everything goes smoothly we respond with a status code of 200 (ok) and send back a JSON with the updated post.

export const deletePost = async (req, res) => { try {  const post = await Post.findOneAndDelete({ _id: req.params.id });  res.status(200).json("Post was deleted"); } catch (error) {  res.status(404).json({ success: false, error }); }}

Lastly, a create a delete function that is finds a post by its id and deletes it for us.

At the end, your controllers > post.js file should look something like this:

import Post from '../models/post.js';export const createPost = async (req, res) => { try {  const post = new Post(req.body);  await post.save();  res.status(201).json(post); } catch (error) {  res.status(400).json({ success: false, error }); }}export const getPosts = async (req, res) => { try {  const posts = await Post.find();  res.status(200).json(posts); } catch (error) {  res.status(400).json({ success: false, error });  }}export const updatePost = async (req, res) => {  const allowedOptions = ['title', 'content', 'tags', 'author'];  const selectedOption = Object.keys(req.body);  const doesExists = selectedOption.every(option => allowedOptions.includes(option)); if (!doesExists) {  return res.status(404).json({ success: false, error }); } try {  const post = await Post.findById({ _id: req.params.id });  selectedOption.forEach(option => post[option] = req.body[option]);  await post.save()  res.status(200).json(post); } catch (error) {  res.status(404).json({ success: false, error }); }}export const deletePost = async (req, res) => { try {  const post = await Post.findOneAndDelete({ _id: req.params.id });  res.status(200).json("Post was deleted"); } catch (error) {  res.status(404).json({ success: false, error }); }}

If you’re still reading, I applaud to you 👏👏👏

PART 11: Setting Up Our Routes

We are nearly done with creating our backend, woohoo!

Navigate to the routes folder and open post.js file.

Inside the post.js file copy and paste the following code:

import express from 'express';const router = express.Router();import { getPosts, createPost, updatePost, deletePost } from '../controllers/posts.js';router.get('/', getPosts);router.post('/', createPost);router.patch('/:id', updatePost);router.delete('/:id', deletePost);export default router;

As usual, we are first importing our express package.

Followed by creating a new instance of Router() and assigning it to router.

Remember the functions we created inside our controllers? Time to import them.

The next 4 lines are type of requests our backend will accept from the frontend.

We got our get (Read).

Post (Create).

Patch (Update), though there is another way to update with express and it is by replacing patch with update. The thing is, patch is a preferred way because it isn’t overriding the entire document, rather it is overriding only what it gets from req.body.

And lastly, we got our delete.

I highly recommend that you check out the documentation express provides and read through it. To learn more, visit this link.

PART 12: The Finish line.

The very last thing you need to do is create a file in the root of your server directory called app.js

This single file is what express will be looking for when starting up our server.

Once you’re inside your app.js file, paste the following:

import express from 'express';
const app = express();
import cors from 'cors';app.use(express.json());app.use(cors());import postRoutes from './routes/posts.js'import './db/db.js';
app.use('/posts', postRoutes)
app.listen(8000)

Like always, first, make sure to import your express package.

Followed by assigning express() to a variable called app (could be named anything, but it is a convention to call it app).

Import the cors package we installed earlier.

Now, since cors is a middleware (to learn more about middleware visit this link) we need to use app.use. Use is a method that accepted two parameters, a path, and a callback. If no path is provided, this tells express to use this middleware on every path. The callback function is our middleware.

The last two imports are our routes and DB that we created earlier.

Here is where things get a little more interesting. When we created our routes in a separate folder, we technically created our very own middleware. To use our middleware, we need to have app.use() run that middleware for us when someone visits your-url.com/posts.

Lastly, we have app.listen() that accepts two parameters, a port, and a callback. The port number could be any number, but usually, port 3000 is for the frontend and port 5000/8000 is for the backend.

Congrats!! You’ve just created your very own backend server that is very capable of doing operations similar to those that are found on popular sites such as Instagram/Facebook.

PART 13: TESTING

Now that we have coded everything, I think it is a good idea to test our backend. Since we currently do not have a frontend, we will need to use a software called postman.

Get Postman here.

Now that we have postman up and running, Time to set it up.

If you haven’t made an account yet, make sure to do so. Otherwise, sign in.

Head to the collection tab, located on the left hand corner, and create a new collection by clicking the + icon.

At the moment our collection is empty, so too populate it, click Add a request.

Give your request a name that makes sense, like post or get.

At this point we are ready to begin testing,

In the field box that asks for request url, type in http://localhost:8000/posts

below it there are a few options, select the option that says body.

Select raw and for format, choose JSON.

Remember our model and its contents, such as title, content. author and tags?

We will use them right now.

Paste the following into the text box.

{"title": "Hello","content": "I am content","author": "Eli","tags": ["a", "b"]}

Click send!

If everything went smoothly, you should get a response that looks something like this.

Now let’s repeat this for the remain crud operations.

Next we will work with read.

Create a new tab and set it to be a get request. Paste the url from the post request.

If everything went great, we should get an array of objects that contains what we created using our post request

Next up, update!

Following the same pattern as with the post request, set the request type to patch and provide it with a URL > Body > Raw > JSON Format.

The URL will be slightly different since we need to include an idea. You can find the id by heading to the previous tab and looking for “_id”. Copy this id and paste it into the tab with our patch.

Our url should look something like this now: http://localhost:8000/posts/600efd7877b0bc057d96a2bd

Let’s update the title, shall we?

Create a an object, provide it a key of “title” and a new value of whatever you want, make sure it is wrapped inside double quotes.

If all is well, you’ll get a response with the updated title.

Lastly, lets delete our beautiful post!

Create a new tab, paste the url you got from update, and set the request to type of delete.

Hopefully with no errors crushing our app, our response should look like this:

Folks, congrats, you have mastered the art of building a crud application from scratch. In part two of this tutorial we will start building our frontend and connecting our backend and frontend together to create a seamless experience.

LINK TO THE REPO

--

--

Eli Sultanov

Sharing with the world things I discover while coding. Find me on Twitter @elidotsv or at https://elisv.com