You are on page 1of 57

Server setup with Express.js and Node.

js
To begin our MERN stack tutorial, we’ll show you how to set up a server with Express.js and
Node.js.

npm package initialization

To create a project folder, enter the folder through the terminal, then run the following
command:

$ npm init
Now it will ask you some questions about package name, version, entry point, etc. Hit enter if
you want to keep the default. After that, you will get something like this:
Select yes and you’re ready to go. It creates a file named package.json.

Installing dependencies

Now, I would like to add some dependencies:

$ npm i express mongoose body-parser bcryptjs validation


Type or copy the command above and hit the enter button. You’ll see something like this:

 bcryptjs is a password hashing function designed by Niels Provos and David Mazières
 body-parser allows us to get the data throughout the request
 express is our main framework
 mongoose is used to connect/interact with MongoDB
 validation (as its name implies) is used for validation

Now I want to add nodemon as a dev dependency. If you don’t want to add this, you can skip it
— it’s optional.

$ npm i -D nodemon
nodemon is a utility that will monitor for any changes in your source and automatically restart
your server.

At that point, your package.json should look like this:


Setting the entry point
Now create a file named app.js for our entry point. You can create this from the project folder
with the command below (on Mac):

$ touch app.js
Then paste the code below:

// app.js

const express = require('express');

const app = express();

app.get('/', (req, res) => res.send('Hello world!'));

const port = process.env.PORT || 8082;

app.listen(port, () => console.log(`Server running on port ${port}`));


Now, run the command

$ node app
You will see Server running on port 8082. You can also check it from the browser: open
the browser and enter http://localhost:8082.

At this point, if we change anything, we need to restart the server manually. But if we set up
nodemon, then we don’t have to restart it every time; nodemon will watch if there is any change
and restart the server automatically.

So what you need to do for that is a little change to the scripts in our package.json file. See
below:

// package.json

{
"name": "mern_a_to_z",
"version": "1.0.0",
"description": "",
"main": "app.js",
"scripts": {
"start": "node app.js",
"app": "nodemon app.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/nurislam03/MERN_A_to_Z.git"
},
"author": "Nur Islam",
"license": "MIT",
"bugs": {
"url": "https://github.com/nurislam03/MERN_A_to_Z/issues"
},
"homepage": "https://github.com/nurislam03/MERN_A_to_Z#readme",
"dependencies": {
"bcryptjs": "^2.4.3",
"body-parser": "^1.19.0",
"express": "^4.17.1",
"mongoose": "^5.5.15",
"validation": "0.0.1"
},
"devDependencies": {
"nodemon": "^1.19.1"
}
}
So, now you can run your project using this command:

$ npm run app


If you get any error at this point, then run the commands below:

$ npm install
$ npm run app
You will see the following changes in your terminal if everything goes right:

Database management with MongoDB


Now it’s time to work on our MERN database setup with MongoDB. For simplicity, we will use
MongoDB Atlas.

Creating an account for MongoDB Atlas


MongoDB Atlas is a fully managed cloud database developed by the same team that built
MongoDB.

First, you need an account. Create one and follow the procedure. After creating an account, you
will see something like this:

Click on the Project 0 section (top left) and you will see a button for creating a new project.
Create a project and select the project.

Now, click on the Build a Cluster button from the project you have created. It will show you all
the information. At the bottom, you will see a section called Cluster Name, click on that and
enter a name for the database, then hit the Create Cluster button.

After two to three minutes, if everything goes well, you will find something like this:
Click on the CONNECT button and fill in the username and password form for your database.
Now hit the Create MongoDB User button. You can also choose either your current IP address
or a different IP address, it’s up to you.

Now, if you follow the CONNECT button or the Choose a connection method button, you will


see some different methods. Select accordingly.
In this case, select the Connect Your Application section.

Now you will get your database link, which we will use in our next step.
Our database is ready — now we need to add it to our project.

Inside the project folder, create another folder named config and inside it create two files
named default.json and db.js. Add the following code:

// default.json
{
"mongoURI":
"mongodb+srv://mern123:<password>@mernatoz-9kdpd.mongodb.net/test?
retryWrites=true&w=majority"
}
/* Replace <password> with your database password */

/* ------------------------------------------------------------------
*/
// db.js

const mongoose = require('mongoose');


const config = require('config');
const db = config.get('mongoURI');

const connectDB = async () => {


try {
await mongoose.connect(
db,
{
useNewUrlParser: true
}
);

console.log('MongoDB is Connected...');
} catch (err) {
console.error(err.message);
process.exit(1);
}
};

module.exports = connectDB;
NOTE: We need a little change in our app.js file to connect to the database. Update
your app.js with this:

// app.js

const express = require('express');


const connectDB = require('./config/db');

const app = express();

// Connect Database
connectDB();

app.get('/', (req, res) => res.send('Hello world!'));


const port = process.env.PORT || 8082;

app.listen(port, () => console.log(`Server running on port ${port}`));


We need another dependency package called config for the global variable to run our project.
Use the following command to install it to the project:

$ npm i config
Now, you can run the project using the following command:

$ npm run app

Great! So far we are on the right track. Our database is successfully connected. Now time to
complete the route setup, and after that, we will see how to create RESTful APIs.

Building RESTful APIs with the MERN stack


Create a folder named routes. In it, create another folder named api, which will hold all our
APIs.

Inside the api folder, create a file named books.js. We will create some APIs here to show
how it works in a moment.

Now update your books.js with the following code:

// routes/api/books.js

const express = require('express');


const router = express.Router();

// Load Book model


const Book = require('../../models/Book');
// @route GET api/books/test
// @description tests books route
// @access Public
router.get('/test', (req, res) => res.send('book route testing!'));

// @route GET api/books


// @description Get all books
// @access Public
router.get('/', (req, res) => {
Book.find()
.then(books => res.json(books))
.catch(err => res.status(404).json({ nobooksfound: 'No Books
found' }));
});

// @route GET api/books/:id


// @description Get single book by id
// @access Public
router.get('/:id', (req, res) => {
Book.findById(req.params.id)
.then(book => res.json(book))
.catch(err => res.status(404).json({ nobookfound: 'No Book found'
}));
});

// @route GET api/books


// @description add/save book
// @access Public
router.post('/', (req, res) => {
Book.create(req.body)
.then(book => res.json({ msg: 'Book added successfully' }))
.catch(err => res.status(400).json({ error: 'Unable to add this
book' }));
});

// @route GET api/books/:id


// @description Update book
// @access Public
router.put('/:id', (req, res) => {
Book.findByIdAndUpdate(req.params.id, req.body)
.then(book => res.json({ msg: 'Updated successfully' }))
.catch(err =>
res.status(400).json({ error: 'Unable to update the Database' })
);
});
// @route GET api/books/:id
// @description Delete book by id
// @access Public
router.delete('/:id', (req, res) => {
Book.findByIdAndRemove(req.params.id, req.body)
.then(book => res.json({ mgs: 'Book entry deleted successfully'
}))
.catch(err => res.status(404).json({ error: 'No such a book' }));
});

module.exports = router;

Database model

In order to interact with our database, we need to create a model for each of our resources. So,
create a folder called models in the root, and inside the models folder, create a file
called Book.js and update it with this:

// models/Book.js

const mongoose = require('mongoose');

const BookSchema = new mongoose.Schema({


title: {
type: String,
required: true
},
isbn: {
type: String,
required: true
},
author: {
type: String,
required: true
},
description: {
type: String
},
published_date: {
type: Date
},
publisher: {
type: String
},
updated_date: {
type: Date,
default: Date.now
}
});

module.exports = Book = mongoose.model('book', BookSchema);


Run the project to see if everything is fine at this point, and you can test all the APIs through
Postman (note that before testing APIs using Postman, you need to run the project first). You
can download Postman here.

Building the frontend


So far, so good! Now that we’ve set up our backend, it’s time to transition to the frontend part of
this MERN stack tutorial.

In this section, we’ll use React to build our user interfaces. React is a JavaScript library for
building user interfaces. It is maintained by Facebook and a community of individual developers
and other companies.

We’ll use Create React App to generate our initial file setup. CRA is a comfortable environment
for learning React and is the best way to start building applications in React. It offers a modern
build setup with no configuration.

We’ll also use webpack and Babel to bundle our modules and compile our JavaScript,
respectively. If you don’t know webpack or Babel well, no problem; you don’t need to install or
configure tools like webpack or Babel. They’re preconfigured and hidden so that you can focus
on the code. Just create a project, and you’re good to go.

You’ll also need any version of Node.js greater than 8.10 and any version of npm greater than
5.6 installed on your local development machine.

Setting up Create React App


Set any directory using a terminal where you want to keep all the files of this project and run the
following command to get the initial setup file:

$ npx create-react-app my-app


You can replace my-app with whatever you’d like to use as your project name. For example, my
project name is mern_a_to_z_client, and my command is:

$ npx create-react-app mern_a_to_z_client


Note: The project name must be in lowercase letters.
If everything goes right, then you will see something like the following image, where you will
find some instructions along with the commands.

Before using any built-in command, we need to go inside the project folder.

$ cd mern_a_to_z_client
Now that we are in the project directory, we can use those available commands. If you’re using
Yarn:

$ yarn start
Or, if using npm:

$ npm start
To run the app in development mode, you can use any of the above commands, and you will see
the following message in your terminal.
Now open http://localhost:3000 to view it in the browser. This page will automatically
reload if you make changes to the code.
Initial project structure
Inside the project directory, our initial file structure should look like this:

Adding Bootstrap and Font Awesome to your React app

We have got our initial setup file for the front-end part. Now we can start integrating our back
end with our front end. Before that, though, I want to add Bootstrap and Font Awesome’s CDN
to our project.

Open the file called index.html, which is in the public


folder mern_a_to_z_client/public/index.html, and replace everything with the
following code:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-
scale=1" />
<meta name="theme-color" content="#000000" />
<!--
manifest.json provides metadata used when your web app is
installed on a
user's mobile device or desktop. See
https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during
the build.
Only files inside the `public` folder can be referenced from the
HTML.

Unlike "/favicon.ico" or "favicon.ico",


"%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root
public URL.
Learn how to configure a non-root public URL by running `npm run
build`.
-->

<!-- bootstrap css cdn -->


<link rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.mi
n.css" integrity="sha384-
Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"
crossorigin="anonymous">

<!-- fontawesome cdn -->


<link rel="stylesheet"
href="https://use.fontawesome.com/releases/v5.2.0/css/all.css"
integrity="sha384-hWVjflwFxL6sNzntih27bfxkr27PmbbK/iSvJ+a4+0owXq79v+ls
FkW54bOGbiDQ" crossorigin="anonymous">

<title>MERN A to Z</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this
app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty
page.

You can add webfonts, meta tags, or analytics to this file.


The build step will place the bundled scripts into the <body>
tag.

To begin the development, run `npm start` or `yarn start`.


To create a production bundle, use `npm run build` or `yarn
build`.
-->

<!-- bootstrap JS cdn -->


<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"
integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/
GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/poppe
r.min.js"
integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPs
kvXusvfa0b4Q" crossorigin="anonymous"></script>
<script
src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.
js"
integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjU
ar5+76PVCmYl" crossorigin="anonymous"></script>

</body>
</html>
Frontend tasks and features
We will work with five different features:

1. Add, create or save a new book


2. Show all the books we have stored in the database
3. Show a single book
4. Update a book
5. Delete a book

Dependencies packages installation

Now, use the following command to add some necessary dependencies:

$ npm install --save react-router-dom


$ npm install --save axios
Why Axios?

Axios is a lightweight HTTP client based similar to a Fetch API. Axios is a promise-based
async/await library for readable asynchronous code. We can easily integrate with React, and it is
effortless to use in any front-end framework.

We’ll call our APIs through Axios.

Package.json file

At this point, our package.json file should be similar to this; versions can be similar or


different:

// MERN_A_to_Z_Client - package.json

{
"name": "mern_a_to_z_client",
"version": "0.1.0",
"private": true,
"dependencies": {
"axios": "^0.19.0",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-router-dom": "^5.0.1",
"react-scripts": "3.0.1"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

Creating the component file

Inside the src folder (mern_a_to_z_client/src/), create another folder


called components, and inside it, create five different files:

1. CreateBook.js
2. ShowBookList.js
3. BookCard.js
4. ShowBookDetails.js
5. UpdateBookInfo.js

We will work with these five files a bit later.

Setup route

Open the folder called App.js inside the src folder (mern_a_to_z_client/src/App.js),


and replace it with the following code:

import React, { Component } from 'react';


import { BrowserRouter as Router, Route } from 'react-router-dom';
import './App.css';

import CreateBook from './components/CreateBook';


import ShowBookList from './components/ShowBookList';
import ShowBookDetails from './components/ShowBookDetails';
import UpdateBookInfo from './components/UpdateBookInfo';

class App extends Component {


render() {
return (
<Router>
<div>
<Route exact path='/' component={ShowBookList} />
<Route path='/create-book' component={CreateBook} />
<Route path='/edit-book/:id' component={UpdateBookInfo} />
<Route path='/show-book/:id' component={ShowBookDetails} />
</div>
</Router>
);
}
}
export default App;
Here, we define all the routes. For a specific path definition, its corresponding component will be
rendered. We have not implemented these files/components yet — just completed the path setup.

Updating the CSS file

Update a CSS file called App.css in the src folder with the following code:

.App {
text-align: center;
}

.App-logo {
animation: App-logo-spin infinite 20s linear;
height: 40vmin;
pointer-events: none;
}

.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}

.App-link {
color: #61dafb;
}

@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

.CreateBook {
background-color: #2c3e50;
min-height: 100vh;
color: white;
}

.ShowBookDetails {
background-color: #2c3e50;
min-height: 100vh;
color: white;
}

.UpdateBookInfo {
background-color: #2c3e50;
min-height: 100vh;
color: white;
}

.ShowBookList {
background-color: #2c3e50;
height: 100%;
width: 100%;
min-height: 100vh;
min-width: 100px;
color: white;
}

/* BookList Styles */
.list {
display: grid;
margin: 20px 0 50px 0;
grid-template-columns: repeat(4, 1fr);
grid-auto-rows: 1fr;
grid-gap: 2em;
}

.card-container {
width: 250px;
border: 1px solid rgba(0,0,.125);
margin: 0 auto;
border-radius: 5px;
overflow: hidden;
}

.desc {
height: 130px;
padding: 10px;
}
.desc h2 {
font-size: 1em;
font-weight: 400;
}

.desc h3, p {
font-weight: 300;
}

.desc h3 {
color: #6c757d;
font-size: 1em;
padding: 10px 0 10px 0;
}
Adding our feature components
Now it’s time to add feature components to our MERN stack project.

Create a new book

Our CreateBook.js file is responsible for adding, creating, or saving a new book or a book’s


info. So, update CreateBook.js with the following code:

import React, { Component } from 'react';


import { Link } from 'react-router-dom';
import '../App.css';
import axios from 'axios';

class CreateBook extends Component {


constructor() {
super();
this.state = {
title: '',
isbn:'',
author:'',
description:'',
published_date:'',
publisher:''
};
}

onChange = e => {
this.setState({ [e.target.name]: e.target.value });
};

onSubmit = e => {
e.preventDefault();

const data = {
title: this.state.title,
isbn: this.state.isbn,
author: this.state.author,
description: this.state.description,
published_date: this.state.published_date,
publisher: this.state.publisher
};

axios
.post('http://localhost:8082/api/books', data)
.then(res => {
this.setState({
title: '',
isbn:'',
author:'',
description:'',
published_date:'',
publisher:''
})
this.props.history.push('/');
})
.catch(err => {
console.log("Error in CreateBook!");
})
};

render() {
return (
<div className="CreateBook">
<div className="container">
<div className="row">
<div className="col-md-8 m-auto">
<br />
<Link to="/" className="btn btn-outline-warning float-
left">
Show BooK List
</Link>
</div>
<div className="col-md-8 m-auto">
<h1 className="display-4 text-center">Add Book</h1>
<p className="lead text-center">
Create new book
</p>

<form noValidate onSubmit={this.onSubmit}>


<div className='form-group'>
<input
type='text'
placeholder='Title of the Book'
name='title'
className='form-control'
value={this.state.title}
onChange={this.onChange}
/>
</div>
<br />

<div className='form-group'>
<input
type='text'
placeholder='ISBN'
name='isbn'
className='form-control'
value={this.state.isbn}
onChange={this.onChange}
/>
</div>

<div className='form-group'>
<input
type='text'
placeholder='Author'
name='author'
className='form-control'
value={this.state.author}
onChange={this.onChange}
/>
</div>

<div className='form-group'>
<input
type='text'
placeholder='Describe this book'
name='description'
className='form-control'
value={this.state.description}
onChange={this.onChange}
/>
</div>

<div className='form-group'>
<input
type='date'
placeholder='published_date'
name='published_date'
className='form-control'
value={this.state.published_date}
onChange={this.onChange}
/>
</div>
<div className='form-group'>
<input
type='text'
placeholder='Publisher of this Book'
name='publisher'
className='form-control'
value={this.state.publisher}
onChange={this.onChange}
/>
</div>

<input
type="submit"
className="btn btn-outline-warning btn-block mt-4"
/>
</form>
</div>
</div>
</div>
</div>
);
}
}

export default CreateBook;

Show all books

The ShowBookList.js component will be responsible for showing all the books we already


have stored in our database. Update ShowBookList.js with this code:
import React, { Component } from 'react';
import '../App.css';
import axios from 'axios';
import { Link } from 'react-router-dom';
import BookCard from './BookCard';

class ShowBookList extends Component {


constructor(props) {
super(props);
this.state = {
books: []
};
}

componentDidMount() {
axios
.get('http://localhost:8082/api/books')
.then(res => {
this.setState({
books: res.data
})
})
.catch(err =>{
console.log('Error from ShowBookList');
})
};

render() {
const books = this.state.books;
console.log("PrintBook: " + books);
let bookList;

if(!books) {
bookList = "there is no book record!";
} else {
bookList = books.map((book, k) =>
<BookCard book={book} key={k} />
);
}

return (
<div className="ShowBookList">
<div className="container">
<div className="row">
<div className="col-md-12">
<br />
<h2 className="display-4 text-center">Books List</h2>
</div>

<div className="col-md-11">
<Link to="/create-book" className="btn btn-outline-
warning float-right">
+ Add New Book
</Link>
<br />
<br />
<hr />
</div>

</div>

<div className="list">
{bookList}
</div>
</div>
</div>
);
}
}

export default ShowBookList;

Creating a card for each book

Here we use a functional component called BookCard.js, which takes a book’s info


from ShowBookList.js and makes a card for each book. Write the following code to update
your BookCard.js file:

import React from 'react';


import { Link } from 'react-router-dom';
import '../App.css';

const BookCard = (props) => {


const book = props.book;

return(
<div className="card-container">
<img src="https://commapress.co.uk/books/the-book-of-
cairo/cairo-provisional-v3/image%2Fspan3" alt="" />
<div className="desc">
<h2>
<Link to={`/show-book/${book._id}`}>
{ book.title }
</Link>
</h2>
<h3>{book.author}</h3>
<p>{book.description}</p>
</div>
</div>
)
};

export default BookCard;


NOTE: Here, I used the same img src for each book, since each book’s respective image may
not always be available. Change the image source, and you can also use a different image for
each book.

Show a book’s info

The ShowBookDetails component has one task: it shows all the info we have about any book.
We have both delete and edit buttons here to get access.

import React, { Component } from 'react';


import { Link } from 'react-router-dom';
import '../App.css';
import axios from 'axios';

class showBookDetails extends Component {


constructor(props) {
super(props);
this.state = {
book: {}
};
}

componentDidMount() {
// console.log("Print id: " + this.props.match.params.id);
axios
.get
('http://localhost:8082/api/books/'+this.props.match.params.id)
.then(res => {
// console.log("Print-showBookDetails-API-response: " +
res.data);
this.setState({
book: res.data
})
})
.catch(err => {
console.log("Error from ShowBookDetails");
})
};

onDeleteClick (id) {
axios
.delete('http://localhost:8082/api/books/'+id)
.then(res => {
this.props.history.push("/");
})
.catch(err => {
console.log("Error form ShowBookDetails_deleteClick");
})
};

render() {

const book = this.state.book;


let BookItem = <div>
<table className="table table-hover table-dark">
{/* <thead>
<tr>
<th scope="col">#</th>
<th scope="col">First</th>
<th scope="col">Last</th>
<th scope="col">Handle</th>
</tr>
</thead> */}
<tbody>
<tr>
<th scope="row">1</th>
<td>Title</td>
<td>{ book.title }</td>
</tr>
<tr>
<th scope="row">2</th>
<td>Author</td>
<td>{ book.author }</td>
</tr>
<tr>
<th scope="row">3</th>
<td>ISBN</td>
<td>{ book.isbn }</td>
</tr>
<tr>
<th scope="row">4</th>
<td>Publisher</td>
<td>{ book.publisher }</td>
</tr>
<tr>
<th scope="row">5</th>
<td>Published Date</td>
<td>{ book.published_date }</td>
</tr>
<tr>
<th scope="row">6</th>
<td>Description</td>
<td>{ book.description }</td>
</tr>
</tbody>
</table>
</div>

return (
<div className="ShowBookDetails">
<div className="container">
<div className="row">
<div className="col-md-10 m-auto">
<br /> <br />
<Link to="/" className="btn btn-outline-warning float-
left">
Show Book List
</Link>
</div>
<br />
<div className="col-md-8 m-auto">
<h1 className="display-4 text-center">Book's Record</h1>
<p className="lead text-center">
View Book's Info
</p>
<hr /> <br />
</div>
</div>
<div>
{ BookItem }
</div>

<div className="row">
<div className="col-md-6">
<button type="button" className="btn btn-outline-danger
btn-lg btn-block"
onClick={this.onDeleteClick.bind(this,book._id)}>Delete
Book</button><br />
</div>

<div className="col-md-6">
<Link to={`/edit-book/${book._id}`} className="btn btn-
outline-info btn-lg btn-block">
Edit Book
</Link>
<br />
</div>

</div>
{/* <br />
<button type="button" class="btn btn-outline-info btn-lg
btn-block">Edit Book</button>
<button type="button" class="btn btn-outline-danger btn-lg
btn-block">Delete Book</button> */}

</div>
</div>
);
}
}

export default showBookDetails;

Update a book’s info

UpdateBookInfo.js, as its name indicates, is responsible for updating a book’s info. An Edit
Book button will trigger this component to perform. After clicking Edit Book, we will see a
form with the old info, which we will be able to edit or replace.

import React, { Component } from 'react';


import { Link } from 'react-router-dom';
import axios from 'axios';
import '../App.css';

class UpdateBookInfo extends Component {


constructor(props) {
super(props);
this.state = {
title: '',
isbn: '',
author: '',
description: '',
published_date: '',
publisher: ''
};
}

componentDidMount() {
// console.log("Print id: " + this.props.match.params.id);
axios
.get
('http://localhost:8082/api/books/'+this.props.match.params.id)
.then(res => {
// this.setState({...this.state, book: res.data})
this.setState({
title: res.data.title,
isbn: res.data.isbn,
author: res.data.author,
description: res.data.description,
published_date: res.data.published_date,
publisher: res.data.publisher
})
})
.catch(err => {
console.log("Error from UpdateBookInfo");
})
};

onChange = e => {
this.setState({ [e.target.name]: e.target.value });
};

onSubmit = e => {
e.preventDefault();

const data = {
title: this.state.title,
isbn: this.state.isbn,
author: this.state.author,
description: this.state.description,
published_date: this.state.published_date,
publisher: this.state.publisher
};

axios
.put
('http://localhost:8082/api/books/'+this.props.match.params.id, data)
.then(res => {
this.props.history.push('/show-
book/'+this.props.match.params.id);
})
.catch(err => {
console.log("Error in UpdateBookInfo!");
})
};

render() {
return (
<div className="UpdateBookInfo">
<div className="container">
<div className="row">
<div className="col-md-8 m-auto">
<br />
<Link to="/" className="btn btn-outline-warning float-
left">
Show BooK List
</Link>
</div>
<div className="col-md-8 m-auto">
<h1 className="display-4 text-center">Edit Book</h1>
<p className="lead text-center">
Update Book's Info
</p>
</div>
</div>

<div className="col-md-8 m-auto">


<form noValidate onSubmit={this.onSubmit}>
<div className='form-group'>
<label htmlFor="title">Title</label>
<input
type='text'
placeholder='Title of the Book'
name='title'
className='form-control'
value={this.state.title}
onChange={this.onChange}
/>
</div>
<br />
<div className='form-group'>
<label htmlFor="isbn">ISBN</label>
<input
type='text'
placeholder='ISBN'
name='isbn'
className='form-control'
value={this.state.isbn}
onChange={this.onChange}
/>
</div>

<div className='form-group'>
<label htmlFor="author">Author</label>
<input
type='text'
placeholder='Author'
name='author'
className='form-control'
value={this.state.author}
onChange={this.onChange}
/>
</div>

<div className='form-group'>
<label htmlFor="description">Description</label>
<input
type='text'
placeholder='Describe this book'
name='description'
className='form-control'
value={this.state.description}
onChange={this.onChange}
/>
</div>

<div className='form-group'>
<label htmlFor="published_date">Published Date</label>
<input
type='date'
placeholder='published_date'
name='published_date'
className='form-control'
value={this.state.published_date}
onChange={this.onChange}
/>
</div>
<div className='form-group'>
<label htmlFor="publisher">Publisher</label>
<input
type='text'
placeholder='Publisher of this Book'
name='publisher'
className='form-control'
value={this.state.publisher}
onChange={this.onChange}
/>
</div>

<button type="submit" className="btn btn-outline-info btn-


lg btn-block">Update Book</button>
</form>
</div>

</div>
</div>
);
}
}

export default UpdateBookInfo;


Connecting the frontend to the backend
We just implemented all of our components! Now we need a little change in our server-side
(back-end) project.

Changes required on the backend

If we try to call our back-end API from the front-end part, it gets an error that says: “Access to
XMLHttpRequest at ‘http://localhost:8082/api/books&#8217; from origin
‘http://localhost:3000&#8217; has been blocked by CORS policy: Response to preflight request
doesn’t pass access control check: No ‘Access-Control-Allow-Origin’ header is present on the
requested resource.”

To solve this, we need to install cors in our back-end (server-side) project. Go to the project
folder (e.g., MERN_A_to_Z) and run:

$ npm install cors


Now, update app.js (the back end’s entry point) with the following code:
// app.js

const express = require('express');


const connectDB = require('./config/db');
var cors = require('cors');

// routes
const books = require('./routes/api/books');

const app = express();

// Connect Database
connectDB();

// cors
app.use(cors({ origin: true, credentials: true }));

// Init Middleware
app.use(express.json({ extended: false }));

app.get('/', (req, res) => res.send('Hello world!'));

// use Routes
app.use('/api/books', books);

const port = process.env.PORT || 8082;

app.listen(port, () => console.log(`Server running on port ${port}`));


Running the frontend and backend
Follow the steps below to run both the frontend and backend of our MERN stack example.

Run the server

Now, run the server (inside the project folder):

$ npm run app


If you get any error, then follow the commands below (inside the project folder):

$ npm install
$ npm run app

Run the client

From the front-end project directory, run the command below:


$ npm start
If you get an error, again, follow the same commands below:

$ npm install
$ npm start
Testing our MERN stack app in the browser
Let’s check everything in the browser. Open http://localhost:3000 in your browser. Now you can
add a book, delete a book, show the list of books, and edit books. The following routes should
perform accordingly:

Add a new book: http://localhost:3000/create-book


Show the list of books: http://localhost:3000/
Show any book’s info: http://localhost:3000/show-book/:id
Update a book’s info: http://localhost:3000/edit-book/:id
Congratulations! You have successfully completed this MERN stack tutorial.
You can visit my GitHub to see both the server-side and client-side portions of this MERN stack
tutorial. You can also find the complete repo for our MERN stack example app on GitHub.

Full visibility into production React apps


Debugging React applications can be difficult, especially when users experience issues that are
hard to reproduce. If you’re interested in monitoring and tracking Redux state, automatically
surfacing JavaScript errors, and tracking slow network requests and component load time, try
LogRocket. 
LogRocket is like a DVR for web and mobile apps, recording literally everything that happens
on your React app. Instead of guessing why problems happen, you can aggregate and report on
what state your application was in when an issue occurred. LogRocket also monitors your app's
performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user
sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your React apps — start monitoring for free.

Share this:

 Twitter

 Reddit

 LinkedIn

 Facebook

 #full stack

 #node

 #react

« Send bitcoin from your wallet using JavaScript


New Material buttons in Flutter »
Creating an RN video calling app with react-native-webrtc

Cristian Rita
May 26, 2022  5 min read

Visualizing GraphQL schema with GraphiQL


Precious Luke
May 26, 2022  4 min read

How to add SwiftUI to a UIKit project

İrem Karaoğlu
May 26, 2022  4 min read

29 Replies to “The MERN stack: A complete tutorial”

1.  Felix Olola Says:

July 24, 2019 at 2:18 pm

Great content. Part 2 please?

2.  Alim Bolar Says:

July 29, 2019 at 5:44 am

Hi.. Any idea when the next part would be available?.. I’ve started this without realising
that this was only one part.. ;-).. so either I wait for our second part or look for another
tutorial.. please advise..

3.  Nur_islam Says:

July 30, 2019 at 2:39 am

hopefully within 2-3 weeks. thanks for following this tutorial.

4.  Nur_islam Says:

July 30, 2019 at 2:42 am

thanks for following this tutorial. you have to wait 2-3 weeks for the 2nd part.

1.  Mr Red Grasshopper Says:


July 31, 2019 at 9:57 am

Thanks… Really good article for me and of course helping me in my study about
mongodb. I can’t wait for the following article.

5.  Farouk BF Says:

August 17, 2019 at 6:15 am

Great tutorial, but isn’t the line


‘app.use(‘/api/books’, books);
missing from app.js if we want to test our routes with Postman/Insomnia?

6.  Mssadewa Says:

August 19, 2019 at 12:02 am

to be able to test the api routes, you will need to add


//in app.js add below
const routes = require('./routes/books');

app.use('/api', routes);

7.  Dmitry H. Says:

August 26, 2019 at 4:14 pm

Guys, the full app.js file is available at Github repo.

8.  Jamal Says:

August 27, 2019 at 7:45 am

Hi, i’m getting the error below when trying to connect:

failed to connect to server [cluster0-shard-00-00-ehcci.mongodb.net:27017] on first connect


[MongoNetworkError: connect ECONNREFUSED 3.227.163.176:27017]

although i have my IP whitlisted and even switched to accepting request from any IP but
still getting this error
9.  JOB Says:

August 28, 2019 at 10:29 pm

When I connect PC’s developing((192.168.1.116:3000 / localhost:3000) to MongoDB


Atlas, that’s OK I can add update DB. So I try to connect other PC same LAN network to
PC’s developing URL:192.168.1.116:3000 I saw the WEB page but I cannot get Book list ,
I cannot ADD book.

How can I solve the problem?

10.  Shakunthala Rani Says:

September 24, 2019 at 4:08 am

Thanks for sharing this information with us !

11.  AV Says:

December 1, 2019 at 2:22 am

I like the content, but why did you install bcryptjs? I don’t see it being used in your app.

12.  Milo MacPhail Says:

January 29, 2020 at 9:41 pm

I’m using this in the class I teach and it’s extremely helpful. Great work!

13.  Avantika Says:

March 16, 2020 at 5:43 am

This post gives a detailed outline of the MERN Stack. Thanks for sharing.

14.  MichaelRydl Says:

April 11, 2020 at 11:50 am


Hello, I´d like to ask, what can I do, if i have this error:

TypeError: connectDB is not a function

at Object. (/mnt/c/Users/Michael/Desktop/WebApps/mern_stack/app.js:7:1)
at Module._compile (internal/modules/cjs/loader.js:955:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:991:10)
at Module.load (internal/modules/cjs/loader.js:811:32)
at Function.Module._load (internal/modules/cjs/loader.js:723:14)
at Function.Module.runMain (internal/modules/cjs/loader.js:1043:10)
at internal/main/run_main_module.js:17:11
[nodemon] app crashed – waiting for file changes before starting…
[nodemon] restarting due to changes…
[nodemon] starting `node app.js`
/mnt/c/Users/Michael/Desktop/WebApps/mern_stack/app.js:7
connectDB();
^

[nodemon] starting `node app.js`


/mnt/c/Users/Michael/Desktop/WebApps/mern_stack/app.js:7
connectDB();
^

Thank you for your response.

1.  Mike Says:

April 27, 2020 at 4:58 pm

Copying and pasting my comment to Aumkar:

I believe the issue may be that you don’t have ‘module.exports = connectDB;’ at the
bottom of the file. So app.js is not actually importing the specific connectDB
function you wrote.

15.  Mike Says:

April 14, 2020 at 12:01 am

There’s a bug in the db.js instructions. The word “parser” should be capitalized in:
useNewUrlparser: true

16.  Mike Says:
April 14, 2020 at 12:24 am

This is a great tutorial! Got one more issue: With just the code in Part 1, it’s not possible to
use Postman to test the APIs. To make it work, app.js needs to be updated to include:

const books = require(‘./routes/api/books’);


app.use(‘/api/books’, books);

That will wire up the APIs so they can be tested.

17.  Aumkar Says:

April 22, 2020 at 5:31 am

Can someone help me? I’m getting the same error as @MichaelRydl, where I am able to
connect to the DB. I replaced the url in the default.json with the url on the ATLAS, with the
username and password that I set for the user, however am still facing this error.

18.  Mike Says:

April 27, 2020 at 4:57 pm

I believe the issue may be that you don’t have ‘module.exports = connectDB;’ at the bottom
of the file. So app.js is not actually importing the specific connectDB function you wrote.

19.  Abhishek Tiwari Says:

June 29, 2020 at 1:13 pm

my all get request are not working

20.  Shaun A Says:

July 28, 2020 at 1:21 am

I had to initialize the body-parser in order to get the server to work.

var bodyParser = require(‘body-parser’)

// parse application/json
app.use(bodyParser.json())
21.  Khushboo Jain Says:

July 28, 2021 at 10:48 am

In add new book details are not submitted

22.  Groovy Web Says:

September 23, 2021 at 2:42 am

You really did hard research and wrote very well. I will try to always keep in mind these
points. Great job

23.  Bagadi Venkat Ramesh Says:

November 11, 2021 at 2:54 am

We should not use the ‘_id’ property created by mongodb since it is a security risk. Refer
to: https://stackoverflow.com/questions/44994863/is-it-a-good-idea-to-use-the-id-value-of-
a-mongodb-object-in-the-dom

24.  Lio Says:

November 27, 2021 at 1:00 am

If you encounter the following error/warning when running the react app:
“Warning: Functions are not valid as a React child.This may happen if you return a
Component instead of from render.”

The error is located in App.js.


The solution to fix this issue is below, pay special attention to how I wrapped each element
in :

class App extends Component {


render() {
return (

<Route exact path='/' element={} />


<Route path='/create-book' element={} />
<Route path='/edit-book/:id' element={} />
<Route path='/show-book/:id' element={} />
);
}
}

25.  Mike Magruder Says:

December 26, 2021 at 11:49 pm

Can someone help? Great tutorial, but for some reason when I update the app.js file with the
recommended code, my database crashes when I try to run it with the following console
message. The db runs without any errors before this step. Any suggestions?

Error: Cannot find module ‘./routes/api/books’


Require stack:
– /Users/michaelbmagruder/Documents/PROJECTS/MERN/project001/app.js
at Function.Module._resolveFilename (internal/modules/cjs/loader.js:889:15)
at Function.Module._load (internal/modules/cjs/loader.js:745:27)
at Module.require (internal/modules/cjs/loader.js:961:19)
at require (internal/modules/cjs/helpers.js:92:18)
at Object.
(/Users/michaelbmagruder/Documents/PROJECTS/MERN/project001/app.js:24:15)
at Module._compile (internal/modules/cjs/loader.js:1072:14)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:1101:10)
at Module.load (internal/modules/cjs/loader.js:937:32)
at Function.Module._load (internal/modules/cjs/loader.js:778:12)
at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:76:12) {
code: ‘MODULE_NOT_FOUND’,
requireStack: [
‘/Users/michaelbmagruder/Documents/PROJECTS/MERN/project001/app.js’
]
}
[nodemon] app crashed – waiting for file changes before starting…

26.  Sarah Says:

March 20, 2022 at 2:47 pm

Hey there! What if my package.json is missing a couple of the dependencies listed in this
project? I went through everything step by step but it seems your package.json has a lot
more scripts and dependencies than mine…

27.  Sarah Says:
March 21, 2022 at 6:55 pm

Has anyone tried using functional components and hooks? My apps front end wont work
because I’m not using functional components

Leave a Reply

Hi there! Would you want to join a virtual meetup on frontend performance


optimization?
X

 Yeah
 No

Great! Sign up for our performance meetup

Have you listened to our podcast? Check it out:


PODROCKET|EP. 26 Redux is alive and well with Mark Erikson
What is modern Redux? What is it with the obsession of declaring Redux dead? In this episode,
Ben and Brian interview Mark Erikson to talk about all things Redux.
LISTEN NOW
PODROCKET|EP. 29 Continuing education with Eve Porcello
In this episode, we talk to Eve Porcello about her experience teaching web development and
Moon Highway, a training and curriculum development company she runs.
LISTEN NOW
Rome and Rome Tools, Inc. with Sebastian
PODROCKET|EP. 34

McKenzie and Jamie Kyle


In this episode, Ben and Kaelan talk to Sebastian McKenzie and Jamie Kyle about Rome Tools,
Inc., the roadmap for Rome, and the experience of getting funding as an open-source tool.
LISTEN NOW
Rocket Surgery: Kaelan and Chris Coyier
PODROCKET|EP. 30

compare notes
Are you up to speed on all of this new CSS stuff? Chris Coyier and Kaelan compare notes on
CSS and frontend development (they also discuss MDN plus).
LISTEN NOW
PODROCKET|EP. 26 Redux is alive and well with Mark Erikson
What is modern Redux? What is it with the obsession of declaring Redux dead? In this episode,
Ben and Brian interview Mark Erikson to talk about all things Redux.
LISTEN NOW
PODROCKET|EP. 29 Continuing education with Eve Porcello
In this episode, we talk to Eve Porcello about her experience teaching web development and
Moon Highway, a training and curriculum development company she runs.
LISTEN NOW
Rome and Rome Tools, Inc. with Sebastian
PODROCKET|EP. 34

McKenzie and Jamie Kyle


In this episode, Ben and Kaelan talk to Sebastian McKenzie and Jamie Kyle about Rome Tools,
Inc., the roadmap for Rome, and the experience of getting funding as an open-source tool.
LISTEN NOW
Rocket Surgery: Kaelan and Chris Coyier
PODROCKET|EP. 30

compare notes
Are you up to speed on all of this new CSS stuff? Chris Coyier and Kaelan compare notes on
CSS and frontend development (they also discuss MDN plus).
LISTEN NOW
PreviousNext
 

You might also like