The MERN stack consists of MongoDB, Express, React / Redux, and Node.js. In this post, we will build a todo application that utilizes a RESTful api which we will also build in the course of this tutorial.
Table content
The MERN stackconsists of MongoDB, Express, React / Redux, and Node.js. Given the popularity of React on the frontend and of Node.js on the backend, the MERN stackis one of the most popular stack of technologies for building a modern single-page application.
In this post, we will build a todo application that utilizes a RESTful APIwhich we will also build in the course of this tutorial. I assume you have little knowledge about javascript(es6), reactjs and nodejs. Since we will be using node and npm, you need to install them to your computer, you should also have your favorite code editor already installed.
Let’s start with the setup. Open your terminal and create a new file directory in any convienient location in your local machine, and you can name it anything.
mkdir Todo
Enter into that file directory
cd Todo
At this stage we need to initialize our project with a package.json file which will contain some information about our app and the dependencies which our app needs to run. You can use npm init
or yarn init
and follow the instructions or you can use npm init -y
to use the default values.
Don’t worry about the content of my package.json file, because i already installed some dependencies before this post. As we progress into the post, yours will probably end up looking like mine.
To run our javascript code on the backend we need to spin up a server which will compile our code. We can create our server in two ways, first is to use the built in http module in node or to make use of express.
For this project we will use express, which is a nodejs framework that handles a lot of things out of the box for us and makes creation of RESTful API a bit simpler. To make use of express, we need to install it using yarn or npm.
yarn add express
ornpm install express
Create a file index.js and type the code below into it and save
const express = require('express');
require('dotenv').config();
const app = express();
const port = process.env.PORT || 5000;
app.use((req, res, next) => {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next();
});
app.use((req, res, next) => {
res.send('Welcome to Express');
});
app.listen(port, () => {
console.log(`Server running on port ${port}`)
});
The code below helps us handle CORS related issues we might face if we try to access our api from a different dormain.
app.use((req, res, next) => {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next();
});
It’s time to start our server to see if it works. Open your terminal in the same directory as your index.js file and type
node index.js
If every thing goes well, you should see Server running on port 5000 in your terminal.
We want to achieve three things with our todo app, which is to create a task, view all task and to delete a completed task. For this, we need to create routes which will define various endpoints that the todo app will depend on. So let’s create a folder routes and create a file api.*_js *_with the following code in it.
const express = require ('express');
const router = express.Router();
router.get('/todos', (req, res, next) => {
});
router.post('/todos', (req, res, next) => {
});
router.delete('/todos/:id', (req, res, next) => {
})
module.exports = router;
Now comes the interesting part, since our app is going to make use of mongodb which is a noSql database, we need to create a model and a schema. Models are defined using the Schema interface. The Schema allows you to define the fields stored in each document along with their validation requirements and default values. In essence the Schema is a blueprint of how the database will be constructed. In addition, you can define static and instance helper methods to make it easier to work with your data types, and also virtual properties that you can use like any other field, but which aren’t actually stored in the database.
To create a Schema and a model we need to install mongoose which is a node package that makes working with mongodb easier.
yarn add mongoose
ornpm install mongoose
Create a new folder in your root directory and name it models, inside it create a file and name it todo.js with the following code in it.
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
//create schema for todo
const TodoSchema = new Schema({
action: {
type: String,
required: [true, 'The todo text field is required']
}
})
//create model for todo
const Todo = mongoose.model('todo', TodoSchema);
module.exports = Todo;
Having created our model we need to update our routes to make use of the new model.
const express = require ('express');
const router = express.Router();
const Todo = require('../models/todo');
router.get('/todos', (req, res, next) => {
//this will return all the data, exposing only the id and action field to the client
Todo.find({}, 'action')
.then(data => res.json(data))
.catch(next)
});
router.post('/todos', (req, res, next) => {
if(req.body.action){
Todo.create(req.body)
.then(data => res.json(data))
.catch(next)
}else {
res.json({
error: "The input field is empty"
})
}
});
router.delete('/todos/:id', (req, res, next) => {
Todo.findOneAndDelete({"_id": req.params.id})
.then(data => res.json(data))
.catch(next)
})
module.exports = router;
We need a database where we will store our data. For this we will make use of mlab. Follow this doc to get started with mlab. After setting up your database you need to update index.js file with the following code
const express = require('express');
const bodyParser = require('body-parser');
const mongoose = require('mongoose');
const routes = require('./routes/api');
const path = require('path');
require('dotenv').config();
const app = express();
const port = process.env.PORT || 5000;
//connect to the database
mongoose.connect(process.env.DB, { useNewUrlParser: true })
.then(() => console.log(`Database connected successfully`))
.catch(err => console.log(err));
//since mongoose promise is depreciated, we overide it with node's promise
mongoose.Promise = global.Promise;
app.use((req, res, next) => {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next();
});
app.use(bodyParser.json());
app.use('/api', routes);
app.use((err, req, res, next) => {
console.log(err);
next();
});
app.listen(port, () => {
console.log(`Server running on port ${port}`)
});
In the code above we made use of process.env to access the environment variable which we will create now. Create a file in your root directory with name *_.env *_and write
DB = ‘mongodb://:@ds039950.mlab.com:39950/todo’
Make sure you use your own mongodb url gotten from mlab after creating your database and user. Replace ** with the username and ** with the password of the user you created. To work with environment variable we have to install a node package called dotenv which makes sure we have access to environment variable stored in the .env file.
yarn add dotenv
ornpm install dotenv
Then we require and configure it in our index.js file
require('dotenv').config()
The reason why i choose to adopt using environment variable in our project is that we can hide any sensitive information from our versioning system, so it’s more of security reasons.
This is the part we start trying out things to make sure our RESTful api is working. Since our frontend is not ready yet, we can make use of some api development clients to test our code. I recommend you make use of Postman or Insomnia, if there’s any other client you have, you can still make use of it. Start your server using node index.js
then open your client, create a get method and navigate to http://localhost:5000/api/todos You should test all the api endpoints and make sure they are working. For the endpoints that require body, you should send json back with the necessary fields since it’s what we setup in our code.
Since we are done with the functionality we want from our api, it’s time to create an interface for the client to interact with the api. To start out with the frontend of the todo app, we will use create react app to scaffold our app, so we won’t have to worry ourselves with setting up webpack and babel from scratch. In the same root directory as your backend code, which is the todo directory, run create-react-app client
or npx create-react-app client
if you don’t want to install create-react-app globally on your computer. This will create a new folder in your root directory called client, where we will be writing all our react code.
After creating the react app, the next thing is to see if the app is well connected and working as well. But before we test it, we need to perform some actions first.
yarn add concurrently --dev
or npm install concurrently --save-dev.
The advantage of concurrently is that it allows us to run more than one command simultaneously from the same terminal window.yarn add nodemon --dev
or npm install nodemon --save-dev
. We will use nodemon to spin up our server and monitor it as well, so that if there is any change in our server, nodemon will restart it automatically for us. {
"name": "todo",
"version": "1.0.0",
"description": "building todo app using mongodb, express, react and nodejs",
"main": "index.js",
"scripts": {
"start": "node index.js",
"start-watch": "nodemon index.js",
"dev": "concurrently \"yarn run start-watch\" \"cd client && yarn start\""
},
"author": "Ojini Jude",
"license": "MIT",
"dependencies": {
"body-parser": "^1.18.3",
"dotenv": "^6.1.0",
"express": "^4.16.4",
"mongoose": "^5.3.6"
},
"devDependencies": {
"concurrently": "^4.0.1",
"nodemon": "^1.18.4"
}
}
"proxy": "http://localhost:5000"
Open your terminal and run npm run dev
and make sure you are in the todo directory and not in the client directory. Voila!, your app should be open and running on localhost:3000.
One of the beautiful things about react is that it makes use of components, which are reusable and also makes ones code modular. For our todo app, we will create two state components and one stateless component. Inside your src folder create another folder called components and inside it create three files Input.js, ListTodo.js and Todo.js.
Open Input.js file and paste the following
import React, { Component } from 'react';
import axios from 'axios';
class Input extends Component {
state = {
action: ""
}
addTodo = () => {
const task = {action: this.state.action}
if(task.action && task.action.length > 0){
axios.post('/api/todos', task)
.then(res => {
if(res.data){
this.props.getTodos();
this.setState({action: ""})
}
})
.catch(err => console.log(err))
}else {
console.log('input field required')
}
}
handleChange = (e) => {
this.setState({
action: e.target.value
})
}
render() {
let { action } = this.state;
return (
add todo
)
}
}
export default Input
To make use of axios, which is a Promise based HTTP client for the browser and node.js, you need to cd into your client from your terminal and run yarn add axios or npm install axios
After that open your ListTodo.js file and paste the following code
import React from 'react';
const ListTodo = ({ todos, deleteTodo }) => {
return (
{todos &&todos.length > 0 ?(todos.map(todo => {return ( deleteTodo(todo._id)}>{todo.action})})):(* No todo(s) left
)}
)
}
export default ListTodo
Then in your Todo.js file you write the following code
import React, {Component} from 'react';
import axios from 'axios';
import Input from './Input';
import ListTodo from './ListTodo';
class Todo extends Component {
state = {
todos: []
}
componentDidMount(){
this.getTodos();
}
getTodos = () => {
axios.get('/api/todos')
.then(res => {
if(res.data){
this.setState({
todos: res.data
})
}
})
.catch(err => console.log(err))
}
deleteTodo = (id) => {
axios.delete(`/api/todos/${id}`)
.then(res => {
if(res.data){
this.getTodos()
}
})
.catch(err => console.log(err))
}
render() {
let { todos } = this.state;
return(
# My Todo(s)
)
}
}
export default Todo;
We need to make little adjustment to our react code, we delete the logo and adjust our App.js to look like this.
import React from 'react';
import Todo from './components/Todo';
import './App.css';
const App = () => {
return (
);
}
export default App;
Then we paste the following code into our App.css file.
.App {
text-align: center;
font-size: calc(10px + 2vmin);
width: 60%;
margin-left: auto;
margin-right: auto;
}
input {
height: 40px;
width: 50%;
border: none;
border-bottom: 2px #101113 solid;
background: none;
font-size: 1.5rem;
color: #787a80;
}
input:focus {
outline: none;
}
button {
width: 25%;
height: 45px;
border: none;
margin-left: 10px;
font-size: 25px;
background: #101113;
border-radius: 5px;
color: #787a80;
cursor: pointer;
}
button:focus {
outline: none;
}
ul {
list-style: none;
text-align: left;
padding: 15px;
background: #171a1f;
border-radius: 5px;
}
li {
padding: 15px;
font-size: 1.5rem;
margin-bottom: 15px;
background: #282c34;
border-radius: 5px;
overflow-wrap: break-word;
cursor: pointer;
}
@media only screen and (min-width: 300px) {
.App {
width: 80%;
}
input {
width: 100%
}
button {
width: 100%;
margin-top: 15px;
margin-left: 0;
}
}
@media only screen and (min-width: 640px) {
.App {
width: 60%;
}
input {
width: 50%;
}
button {
width: 30%;
margin-left: 10px;
margin-top: 0;
}
}
Also in our index.css file we write the following styling to it.
body {
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
box-sizing: border-box;
background-color: #282c34;
color: #787a80;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
monospace;
}
Assuming we encounter no errors when we saved our code, we should have a working todo app. That has the basic functionality we discussed earlier, which is creating a task, deleting a task and viewing all your task.
A journey of a thousand miles, begins with a step. I believe you’ve taken that bold step towards learning and understanding the MERN stack. Meanwhile i have a little task for you guys. if you’ve noticed, our todo app doesn’t have editing functionality, so i want you guys to do two things; first is to write the api code to implement updating a previous data in the database and secondly is to add two buttons on each task, edit and delete button, so that when as a user, i click the edit button, a popup shows, where i edit that task and save. When you are done feel free to share your github link in the comment, also if you face any issue let’s discuss it as well.
#mongodb #express #reactjs #redux #node-js #javascript