DEV Community

Cover image for How to develop MERN stack app
Adeolu Oyinlola
Adeolu Oyinlola

Posted on • Edited on

How to develop MERN stack app

Hello guys,
The question I intend to answer with this tutorial is; how do I develop a MERN Stack app? So, by the end of this tutorial you will be able to develop MERN app in CRUD(create, Read, Update and Delete) form.

I will assume you have some basic understanding of MERN stack as one of the most relevant app development stack in the world which is growing fast every day, use by many developers around the globe with a huge community support.
Simply put, a MERN stack is a combination of four technologies: Mongo DB, Express JS, React JS, and Node JS. More importantly, MERN Stack is combination of open source database and Javascript library & runtime environment. Each letter of this acronym means;

  • Mongo DB: A document-based open-source database, that provides scalability and flexibility.
  • Express JS: A structured base designed to develop web applications and APIs.
  • React JS: A Javascript Front-end library for building user interfaces. Maintained by Facebook.
  • Node JS: A javascript runtime environment built on Chrome’s V8 JS engine.

Alternatively, you might come across another acronym very similar to MERN, called MEAN, there is, it’s almost the same, but the difference is that we use Angular instead of React.

Note that this tutorial is the first part of MERN stack beginner’s series(part 2 - How to Dockerize MERN stack app in AWS EC2 & part 3 - How to Deploy MERN stack app on AWS ECS, it is advised to follow this sequentially. Also, find the links to the remaining part at the end this tutorial). So, I’m going to teach you how to develop a simple MERN stack Application.

I believe you will agree with me that, the best way to learn anything is by doing it. So, I will guide you into developing this simple app using MERN stack, here we’re going to create a book rating web app called bukrate in CRUD (Create, Read, Update and Delete) form. For proper understanding, I will break this tutorial into four sections; Requirements, Setting up server side, Setting up client side and Integrating server & client side

REQUIREMENTS;
Basic requirement includes;
a)basic javascript, HTML, CSS
b)code editor; preferably VS Code
c)setup development environment on your laptop/desktop
d)install browser, preferably google chrome
e)install nodejs
f)setup MongoDB Atlas

1.0 SETTING UP SERVER SIDE

In this section, we’re going to create the server of our application, where we’re going to create a RESTful API following the steps below.
Firstly, we create an empty directory that will be the root of our system.


$ mkdir bukrate-app
$ cd bukrate-app

Enter fullscreen mode Exit fullscreen mode

The above directory represent folder for our project - bukrate app. Inside this bukrate-app, let’s now create another empty folder called server that will be our backend folder.


$ mkdir server
$ cd server

Enter fullscreen mode Exit fullscreen mode

Follow by creating our package.json inside this server folder.
So, what’s a package.json and how can we create one?

package.json file is the heart of Node. js system. It is the manifest file of any Node. js project and contains the metadata of the project which is required before publishing to NPM, and also defines functional attributes of a project that npm uses to install dependencies, run scripts, and identify the entry point to our package.

To create a package.json you need a Package Manager, preferably choose NPM (Node Package Manager) which we will be using in this tutorial. Feel free to use what you prefer say alternatively YARN

Now we can create our file using NPM.


$ npm init -y

Enter fullscreen mode Exit fullscreen mode

While creating this file, you’ll be asked questions related to your project. If you’d like to keep the default provided by NPM, you can just type enter until the end.
After that, you can find the file in the server folder. Then we'll able to install our dependencies. Check out the list and uses of our dependencies;

  • Express: It’s the server framework (The E in MERN).
  • Body Parser: Responsible to get the body off of network requests.
  • Nodemon: Restart the server when it sees changes (for a better dev experience).
  • Cors: Package for providing a Connect/Express middleware that can be used to enable CORS with various options.
  • Mongoose: It’s an elegant MongoDB object modeling for node.js

Then, install all the dependencies by running this command;

$ npm install express body-parser cors mongoose nodemon
Enter fullscreen mode Exit fullscreen mode

Immediately, after successfully create these dependencies, you will found additional two new folder node_modules and package-lock.json. With these two files, your project can be interpreted.

From here on, we can now create NodeJS file(at the root of server folder) called index.js which contain all the logic for our API/server as follow.

const express = require('express')
const bodyParser = require('body-parser')
const cors = require('cors')
const mongoose = require('mongoose')
const app = express()
const apiPort = 3000

app.use(bodyParser.urlencoded({ extended: true }))
app.use(cors())
app.use(bodyParser.json())

app.get('/', (req, res) => {
    res.send('Hello World!')
})

app.listen(apiPort, () => console.log(`Server running on port ${apiPort}`))
Enter fullscreen mode Exit fullscreen mode

Image description
To start the application you just need to run the following command:

node index.js
Enter fullscreen mode Exit fullscreen mode

You should see the message “Server running on port 3000” it means that everything you’ve done until now it’s correct.
Then open a browser and type localhost:3000, you’ll see the message “Hello World”.

The good news is, you’re able to see your server running. Next is to set up database for our application.

1.1 Setup Cloud MongoDB
We need to either install MongoDB on the computer locally or use the cloud database. For this project we use the cloud - Atlas;
This is simple, you can just follow the step provided by MongoDB documentation here. MongoDB
In an attempt not to make this tutorial unnecessarily long, I will not spend much time on setting up the database, but will try to highlight each step, also this tutorial can be of great help Link;
a)sign up Here
b)choose Atlas
c)build cluster
d)named the cluster if choose to
e)build database; - by clicking on the collection. - Add My Own Data. - Add Database and Collection name
f)Setup Database Access; - to add user
g)connect to the app database; - connect to your application, - add method.
With this, we’ve just created our database with these steps.

Now, let's return back to our javascript code, we need to create a connection from our server using the Mongoose library.

Now, let's update the main Nodejs file server/index.js we have something like this.

const express = require('express')
const bodyParser = require('body-parser')
const cors = require('cors')
const mongoose = require('mongoose')
const app = express()

//connect to mongodb
const dbURI = 'mongodb+srv://<username>:<password>@bukrate-app.lfqmj.mongodb.net/bukrate?retryWrites=true&w=majority';

mongoose.connect(dbURI, { useNewUrlParser: true, useUnifiedTopology: true })
  .then(() => app.listen(PORT, () => console.log(`Server Running on Port: http://localhost:${PORT}`)))
  .catch((error) => console.log(`${error} did not connect`));

const apiPort = 3000

app.use(bodyParser.urlencoded({ extended: true }))
app.use(cors())
app.use(bodyParser.json())

app.get('/', (req, res) => {
    res.send('Hello World!')
})

app.listen(apiPort, () => console.log(`Server running on port ${apiPort}`))
Enter fullscreen mode Exit fullscreen mode

You should notice I have added like five line after the comment //connect to mongodb
Because of this update, let's restart the application to see the effect, then you need to close and open a new one. This is where nodemon will start its working, it’s going to restart the server whenever it sees changes in any file of our project. So, let’s do this by running the command.

$ nodemon index.js
Enter fullscreen mode Exit fullscreen mode

Image description

1.1.1. Creating Book’s Schema
We need to create an entity called book that should be composed of the name of a book. For this, we just need to know the name of the book and the author of the book, and to add an important information, the rating.

Let’s go ahead and create a folder called models and add a file called rate.js.

$ mkdir models
$ cd models
$ touch rate.js
Enter fullscreen mode Exit fullscreen mode
const mongoose = require('mongoose')
const Schema = mongoose.Schema

const rateSchema = new Schema(
    {
        name: { type: String, required: true },
        author: { type: String, required: true },
        rating: { type: Number, required: true },
    },
    { timestamps: true },
)

const Rate = mongoose.model('Rate', rateSchema);
module.exports = Rate;
Enter fullscreen mode Exit fullscreen mode

1.2. Getting and Saving Data
Here, we’ll create all the CRUD operations and create our REST endpoints. Let’s create two more folders inside the server: routes and controllers. In the route folder, let’s create the file rate-router.js and in the controller folder, rate-ctrl.js.

$ mkdir routes controllers
$ touch routes/rate-router.js
$ touch controllers/rate-ctrl.js
Enter fullscreen mode Exit fullscreen mode
const Rate = require('../models/rate')

createRate = (req, res) => {
    const body = req.body

    if (!body) {
        return res.status(400).json({
            success: false,
            error: 'You must provide a rate',
        })
    }

    const rate = new Rate(body)

    if (!rate) {
        return res.status(400).json({ success: false, error: err })
    }

    rate
        .save()
        .then(() => {
            return res.status(201).json({
                success: true,
                id: rate._id,
                message: 'rate created!',
            })
        })
        .catch(error => {
            return res.status(400).json({
                error,
                message: 'Rate not created!',
            })
        })
}

updateRate = async (req, res) => {
    const body = req.body

    if (!body) {
        return res.status(400).json({
            success: false,
            error: 'You must provide a body to update',
        })
    }

    Rate.findOne({ _id: req.params.id }, (err, rate) => {
        if (err) {
            return res.status(404).json({
                err,
                message: 'Rate not found!',
            })
        }
        rate.name = body.name
        rate.author = body.time
        rate.rating = body.rating
        rate
            .save()
            .then(() => {
                return res.status(200).json({
                    success: true,
                    id: rate._id,
                    message: 'Rate updated!',
                })
            })
            .catch(error => {
                return res.status(404).json({
                    error,
                    message: 'Rate not updated!',
                })
            })
    })
}

deleteRate = async (req, res) => {
    await Rate.findOneAndDelete({ _id: req.params.id }, (err, rate) => {
        if (err) {
            return res.status(400).json({ success: false, error: err })
        }

        if (!rate) {
            return res
                .status(404)
                .json({ success: false, error: `Rate not found` })
        }

        return res.status(200).json({ success: true, data: rate })
    }).catch(err => console.log(err))
}

getRateById = async (req, res) => {
    await Rate.findOne({ _id: req.params.id }, (err, rate) => {
        if (err) {
            return res.status(400).json({ success: false, error: err })
        }

        if (!rate) {
            return res
                .status(404)
                .json({ success: false, error: `Rate not found` })
        }
        return res.status(200).json({ success: true, data: rate })
    }).catch(err => console.log(err))
}

getRates = async (req, res) => {
    await Rate.find({}, (err, rates) => {
        if (err) {
            return res.status(400).json({ success: false, error: err })
        }
        if (!rates.length) {
            return res
                .status(404)
                .json({ success: false, error: `Rate not found` })
        }
        return res.status(200).json({ success: true, data: rates })
    }).catch(err => console.log(err))
}

module.exports = {
    createRate,
    updateRate,
    deleteRate,
    getRates,
    getRateById,
}
Enter fullscreen mode Exit fullscreen mode
const express = require('express')

const RateCtrl = require('../controllers/rate-ctrl')

const router = express.Router()

router.post('/rate', RateCtrl.createRate)
router.put('/rate/:id', RateCtrl.updateRate)
router.delete('/rate/:id', RateCtrl.deleteRate)
router.get('/rate/:id', RateCtrl.getRateById)
router.get('/rates', RateCtrl.getRates)

module.exports = router
Enter fullscreen mode Exit fullscreen mode

Lastly, let’s add the router in our server/index.js file.

const express = require('express')
const bodyParser = require('body-parser')
const cors = require('cors')
const mongoose = require('mongoose')
const Rate = require('./models/rate')
const app = express()

//connect to mongodb
const dbURI = 'mongodb+srv://<username>:<password>@bukrate-app.lfqmj.mongodb.net/bukrate?retryWrites=true&w=majority';

mongoose.connect(dbURI, { useNewUrlParser: true, useUnifiedTopology: true })
  .then(() => app.listen(PORT, () => console.log(`Server Running on Port: http://localhost:${PORT}`)))
  .catch((error) => console.log(`${error} did not connect`));


const rateRouter = require('./routes/rate-router')
const apiPort = 3000

app.use(bodyParser.urlencoded({ extended: true }))
app.use(cors())
app.use(bodyParser.json())

app.get('/', (req, res) => {
    res.send('Hello World!')
})

app.use('/api', rateRouter)

app.listen(apiPort, () => console.log(`Server running on port ${apiPort}`))
Enter fullscreen mode Exit fullscreen mode

1.2.1 Protecting Your Database
Up till here, there shouldn't be any issue with our application. But if you want to test the application you can use either Postman or Robo 3T. These tools help us in verify our application workings (You can find how to from the documentation on their websites or other resources on google).
Remember how you have to input your database access's username and password during the MongoDB setup. So, imagine you have a course to share your source code to the public, say on GitHub, you need to protect access to your database.
Firstly, we need another dependencies to run;

$ npm install dotenv
Enter fullscreen mode Exit fullscreen mode

Then, create .env file server/.env

Inside index.js, add and update with another variables;
const dotenv = require('dotenv')
Also cut the MongoDB link from line 12 and replace with
process.env.MONGO_URL
Then inside .env file;

MONGO_URL = mongodb+srv://<username>:<password>@bukrate-app.lfqmj.mongodb.net/bukrate?retryWrites=true&w=majority
Enter fullscreen mode Exit fullscreen mode

Congratulation! We’ve just finished the server side. With all these knowledge you’re now in position to create other entities, try to create a user entity, maybe an employee or a table price, use your imagination is your limitation.

Let’s go straight up to setting up client side.

2.0 SETTING UP CLIENT SIDE
This is where we’re going to create all the user-interface parts, where the user will interact with our application.
Firstly, we’re going to the root of our project and create the client-side. For this, we need to understand about NPX.
NPX is a tool that its goal is to help round out the experience of using packages from the NPM registry. As NPM makes it super easy to install and manage dependencies hosted on the registry, NPX makes it easy to use CLI tools and other executables hosted on the registry.
Let’s create our directory. So, the server-side will be for the Backend and we have the client-side for the Frontend.

$ npx create-react-app client 
$ cd client
$ npm start
Enter fullscreen mode Exit fullscreen mode

The application should opened this screen if you get it right.

Image description
You will remember that the default port is 3000, but we’ve already set this port to our Backend. Then, let’s changes it to 7000. Meanwhile, you choose whatever number.
To change the port we need to change it on client/package.json. Include PORT= in the file.

...
},
  "scripts": {
    "start": "set PORT=7000 && react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
...
Enter fullscreen mode Exit fullscreen mode

You will discover that React creates some default files. Let’s remove some of the unnecessaries files for us;
$ rm App.css index.css App.test.js serviceWorker.js

To view the complete app, we need to set up our project by installing some other dependencies like we do in the server side. These are what we need; Axios, Bootstrap, Styled-Components, and React Table.

  • axios: It’s a promise-based the asynchronous code. It’s the most popular promise based HTTP.
  • bootstrap: It’s is an open-source toolkit and the most popular front-end component library where allows you for developing with HTML, CSS, and JS.
  • styled-components: It allows you to write actual CSS code to style your components.
  • react-table: It’s a lightweight, fast, and extendable data grid built for React.
  • react-router-dom: DOM bindings for React Routers.
$ npm install styled-components react-table react-router-dom axios bootstrap --save
Enter fullscreen mode Exit fullscreen mode

In the src directory, we should create the new directories that will be the structure of our project. Create an index.js file inside each directory, except the app folder.

$ cd src
$ mkdir api app components pages style
$ touch api/index.js components/index.js pages/index.js style/index.js
Enter fullscreen mode Exit fullscreen mode

Move the App.js file to the app directory, but renaming to index.js.

Now is time for us to start the coding,
First of all, update our file client/src/index.js for the following code.

import React from 'react'
import ReactDOM from 'react-dom'
import App from './app'

ReactDOM.render(<App />, document.getElementById('root'))
Enter fullscreen mode Exit fullscreen mode

After this, we’ll develop the header of the application. And create the other components of our project. Create the new files NavBar.jsx, Logo.jsx, and Links.jsx.

$ touch components/NavBar.jsx components/Logo.jsx components/Links.jsx
Enter fullscreen mode Exit fullscreen mode

Hey! What is JSX extension?
Simple, JSX is a notation that Reacts chose to identify a JavaScript’s eXtension. It’s recommended to use it with React to describe what the UI should look like.

Now, let’s create our files.
For the Links;

import React, { Component } from 'react'
import { Link } from 'react-router-dom'
import styled from 'styled-components'

const Collapse = styled.div.attrs({
    className: 'collpase navbar-collapse',
})``

const List = styled.div.attrs({
    className: 'navbar-nav mr-auto',
})``

const Item = styled.div.attrs({
    className: 'collpase navbar-collapse',
})``

class Links extends Component {
    render() {
        return (
            <React.Fragment>
                <Link to="/" className="navbar-brand">
                    MERN Application
                </Link>
                <Collapse>
                    <List>
                        <Item>
                            <Link to="/rates/list" className="nav-link">
                                List Book Rates
                            </Link>
                        </Item>
                        <Item>
                            <Link to="/rates/create" className="nav-link">
                                Create Book Rate
                            </Link>
                        </Item>
                    </List>
                </Collapse>
            </React.Fragment>
        )
    }
}

export default Links
Enter fullscreen mode Exit fullscreen mode

For the Logo;

import React, { Component } from 'react'
import styled from 'styled-components'

import logo from '../logo.svg'

const Wrapper = styled.a.attrs({
    className: 'navbar-brand',
})``

class Logo extends Component {
    render() {
        return (
            <Wrapper href="https://deoluoyinlola.netlify.app">
                <img src={logo} width="50" height="50" alt="deoluoyinlola.netlify.app" />
            </Wrapper>
        )
    }
}

export default Logo
Enter fullscreen mode Exit fullscreen mode

For the NavBar;

import React, { Component } from 'react'
import styled from 'styled-components'

import Logo from './Logo'
import Links from './Links'

const Container = styled.div.attrs({
    className: 'container',
})``

const Nav = styled.nav.attrs({
    className: 'navbar navbar-expand-lg navbar-dark bg-dark',
})`
    margin-bottom: 20 px;
`

class NavBar extends Component {
    render() {
        return (
            <Container>
                <Nav>
                    <Logo />
                    <Links />
                </Nav>
            </Container>
        )
    }
}

export default NavBar
Enter fullscreen mode Exit fullscreen mode

The components/index.js file will help to export our components, it will allow us to import them in other files using the notation; import { blabla } from './components'

import Links from './Links'
import Logo from './Logo'
import NavBar from './NavBar'

export { Links, Logo, NavBar }
Enter fullscreen mode Exit fullscreen mode

Lastly, let's update app/index.js. Then, we able to see the development of the project.

import React from 'react'
import { BrowserRouter as Router } from 'react-router-dom'

import { NavBar } from '../components'

import 'bootstrap/dist/css/bootstrap.min.css'

function App() {
    return (
        <Router>
            <NavBar />
        </Router>
    )
}

export default App
Enter fullscreen mode Exit fullscreen mode

Image description
3.0 INTEGRATING SERVER AND CLIENT SIDE
Now let's learn how to integrate the server with our client.
First of all, let’s update the file api/index.js.

import axios from 'axios'

const api = axios.create({
    baseURL: 'http://localhost:3000/api',
})

export const insertRate = payload => api.post(`/rate`, payload)
export const getAllRates = () => api.get(`/rates`)
export const updateRateById = (id, payload) => api.put(`/rate/${id}`, payload)
export const deleteRateById = id => api.delete(`/rate/${id}`)
export const getRateById = id => api.get(`/rate/${id}`)

const apis = {
    insertRate,
    getAllRates,
    updateRateById,
    deleteRateById,
    getRateById,
}

export default apis
Enter fullscreen mode Exit fullscreen mode

Amazing! Now we can develop our routes. For this, we’ll need to update the file app/index.js adding the routes.

import React from 'react'
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'

import { NavBar } from '../components'
import { RatesList, RatesInsert, RatesUpdate } from '../pages'

import 'bootstrap/dist/css/bootstrap.min.css'

function App() {
    return (
        <Router>
            <NavBar />
            <Switch>
                <Route path="/rates/list" exact component={RatesList} />
                <Route path="/rates/create" exact component={RatesInsert} />
                <Route
                    path="/rates/update/:id"
                    exact
                    component={RatesUpdate}
                />
            </Switch>
        </Router>
    )
}

export default App
Enter fullscreen mode Exit fullscreen mode

On pages folder, let’s create the files that will do the role of each application’s page: RatesList.jsx, RatesInsert.jsx and RatesUpdate.jsx.

$ cd pages
$ touch RatesList.jsx RatesInsert.jsx RatesUpdate.jsx
Enter fullscreen mode Exit fullscreen mode

For now, let’s create simples files where you can see the page’s transition when you click in each link of the NavBar.

For RateList;

import React, { Component } from 'react'

class RatesList extends Component {
    render() {
        return (
            <div>
                <p>In this page you'll see the list of rates</p>
            </div>
        )
    }
}

export default RatesList
Enter fullscreen mode Exit fullscreen mode

For RateInsert;

import React, { Component } from 'react'

class RatesInsert extends Component {
    render() {
        return (
            <div>
                <p>In this page you'll see the form to add a book</p>
            </div>
        )
    }
}

export default RatesInsert
Enter fullscreen mode Exit fullscreen mode

For RateUpdate;

import React, { Component } from 'react'

class RatesUpdate extends Component {
    render() {
        return (
            <div>
                <p>In this page you'll see the form to update the books</p>
            </div>
        )
    }
}

export default RatesUpdate
Enter fullscreen mode Exit fullscreen mode

For index.js

import RatesList from './RatesList'
import RatesInsert from './RatesInsert'
import RatesUpdate from './RatesUpdate'

export { RatesList, RatesInsert, RatesUpdate }
Enter fullscreen mode Exit fullscreen mode

OK, let’s edit our file to get the ratings from the database: RatesList.jsx.

import React, { Component } from 'react'
import ReactTable from 'react-table'
import api from '../api'

import styled from 'styled-components'

import 'react-table/react-table.css'

const Wrapper = styled.div`
    padding: 0 40px 40px 40px;
`

class RatesList extends Component {
    constructor(props) {
        super(props)
        this.state = {
            rates: [],
            columns: [],
            isLoading: false,
        }
    }

    componentDidMount = async () => {
        this.setState({ isLoading: true })

        await api.getAllRates().then(rates => {
            this.setState({
                rates: rates.data.data,
                isLoading: false,
            })
        })
    }

    render() {
        const { rates, isLoading } = this.state
        console.log('TCL: RatesList -> render -> rates', rates)

        const columns = [
            {
                Header: 'ID',
                accessor: '_id',
                filterable: true,
            },
            {
                Header: 'Name',
                accessor: 'name',
                filterable: true,
            },
            {
                Header: 'Author',
                accessor: 'author',
                filterable: true,
            },
            {
                Header: 'Rating',
                accessor: 'rating',
                filterable: true,
            },
            {
                Header: 'Time',
                accessor: 'time',
                Cell: props => <span>{props.value.join(' / ')}</span>,
            },
        ]

        let showTable = true
        if (!rates.length) {
            showTable = false
        }

        return (
            <Wrapper>
                {showTable && (
                    <ReactTable
                        data={rates}
                        columns={columns}
                        loading={isLoading}
                        defaultPageSize={10}
                        showPageSizeOptions={true}
                        minRows={0}
                    />
                )}
            </Wrapper>
        )
    }
}

export default RatesList

Enter fullscreen mode Exit fullscreen mode

Awesome, in this list let’s include two more things, these things will be buttons. One button to delete another button to update.

import React, { Component } from 'react'
import ReactTable from "react-table-6"
import api from '../api'

import styled from 'styled-components'

import "react-table-6/react-table.css" 

const Wrapper = styled.div`
    padding: 0 40px 40px 40px;
`

const Update = styled.div`
    color: #ef9b0f;
    cursor: pointer;
`

const Delete = styled.div`
    color: #ff0000;
    cursor: pointer;
`

class UpdateRate extends Component {
    updateUser = event => {
        event.preventDefault()

        window.location.href = `/rates/update/${this.props.id}`
    }

    render() {
        return <Update onClick={this.updateUser}>Update</Update>
    }
}

class DeleteRate extends Component {
    deleteUser = event => {
        event.preventDefault()

        if (
            window.confirm(
                `Do you want to delete the movie ${this.props.id} permanently?`,
            )
        ) {
            api.deleteRateById(this.props.id)
            window.location.reload()
        }
    }

    render() {
        return <Delete onClick={this.deleteUser}>Delete</Delete>
    }
}

class RatesList extends Component {
    constructor(props) {
        super(props)
        this.state = {
            rates: [],
            columns: [],
            isLoading: false,
        }
    }

    componentDidMount = async () => {
        this.setState({ isLoading: true })

        await api.getAllRates().then(rates => {
            this.setState({
                rates: rates.data.data,
                isLoading: false,
            })
        })
    }

    render() {
        const { rates, isLoading } = this.state
        console.log('TCL: RatesList -> render -> rates', rates)

        const columns = [
            {
                Header: 'ID',
                accessor: '_id',
                filterable: true,
            },
            {
                Header: 'Name',
                accessor: 'name',
                filterable: true,
            },
            {
                Header: 'Author',
                accessor: 'author',
                filterable: true,
            },
            {
                Header: 'Rating',
                accessor: 'rating',
                filterable: true,
            },
            {
                Header: 'Time',
                accessor: 'time',
                Cell: props => <span>{props.value.join(' / ')}</span>,
            },
            {
                Header: '',
                accessor: '',
                Cell: function(props) {
                    return (
                        <span>
                            <DeleteRate id={props.original._id} />
                        </span>
                    )
                },
            },
            {
                Header: '',
                accessor: '',
                Cell: function(props) {
                    return (
                        <span>
                            <UpdateRate id={props.original._id} />
                        </span>
                    )
                },
            },
        ]

        let showTable = true
        if (!rates.length) {
            showTable = false
        }

        return (
            <Wrapper>
                {showTable && (
                    <ReactTable
                        data={rates}
                        columns={columns}
                        loading={isLoading}
                        defaultPageSize={10}
                        showPageSizeOptions={true}
                        minRows={0}
                    />
                )}
            </Wrapper>
        )
    }
}

export default RatesList
Enter fullscreen mode Exit fullscreen mode

As you can see, now we’re able to delete a book rate, but we still not able to update it, because we haven’t finished the component yet.
Before, to develop the update page, let’s create the insertion page.

import React, { Component } from 'react'
import api from '../api'

import styled from 'styled-components'

const Title = styled.h1.attrs({
    className: 'h1',
})``

const Wrapper = styled.div.attrs({
    className: 'form-group',
})`
    margin: 0 30px;
`

const Label = styled.label`
    margin: 5px;
`

const InputText = styled.input.attrs({
    className: 'form-control',
})`
    margin: 5px;
`

const Button = styled.button.attrs({
    className: `btn btn-primary`,
})`
    margin: 15px 15px 15px 5px;
`

const CancelButton = styled.a.attrs({
    className: `btn btn-danger`,
})`
    margin: 15px 15px 15px 5px;
`

class RatesInsert extends Component {
    constructor(props) {
        super(props)

        this.state = {
            name: '',
            author: '',
            rating: '',
            time: '',
        }
    }

    handleChangeInputName = async event => {
        const name = event.target.value
        this.setState({ name })
    }
    handleChangeInputAuthor = async event => {
        const author = event.target.value
        this.setState({ author })
    }

    handleChangeInputRating = async event => {
        const rating = event.target.validity.valid
            ? event.target.value
            : this.state.rating

        this.setState({ rating })
    }

    handleChangeInputTime = async event => {
        const time = event.target.value
        this.setState({ time })
    }

    handleIncludeRate = async () => {
        const { name, rating, time } = this.state
        const arrayTime = time.split('/')
        const payload = { name, rating, time: arrayTime }

        await api.insertRate(payload).then(res => {
            window.alert(`Book inserted successfully`)
            this.setState({
                name: '',
                author: '',
                rating: '',
                time: '',
            })
        })
    }

    render() {
        const { name, author, rating, time } = this.state
        return (
            <Wrapper>
                <Title>Create Book</Title>

                <Label>Name: </Label>
                <InputText
                    type="text"
                    value={name}
                    onChange={this.handleChangeInputName}
                />

                <Label>Author: </Label>
                <InputText
                    type="text"
                    value={author}
                    onChange={this.handleChangeInputAuthor}
                />

                <Label>Rating: </Label>
                <InputText
                    type="number"
                    step="0.1"
                    lang="en-US"
                    min="0"
                    max="10"
                    pattern="[0-9]+([,\.][0-9]+)?"
                    value={rating}
                    onChange={this.handleChangeInputRating}
                />

                <Label>Time: </Label>
                <InputText
                    type="text"
                    value={time}
                    onChange={this.handleChangeInputTime}
                />

                <Button onClick={this.handleIncludeMovie}>Add Book</Button>
                <CancelButton href={'/rates/list'}>Cancel</CancelButton>
            </Wrapper>
        )
    }
}

export default RatesInsert
Enter fullscreen mode Exit fullscreen mode

And finally, let’s create the update file.

Friend, we have come to the end of this tutorial. In this article, you have seen the structure to build a MERN application using diverse libraries provided by an amazing community around the world.
In this article, my intuit was to keep things simple for your understanding. This is a project that you can do many enhancements.
Maybe you can try, for instance, create a better structure to include the year of the book or you can improve the code to put the update and insertion book in the same file, what about to create a new entity called the customer and create the same RESTful operation for it? You can add a lot of new features to this. If you prefer, tell me in the comments.

You can find the complete github repo Here

Also, is advised to follow up this tutorial up with the next part - How Dockerize MERN App on AWS EC2, Link here
I hope that I have contributed to your knowledge. Feel free to tell me what I should improve on to write better articles.

I love to see you in the next part.

Top comments (0)