Learning Objectives
By the end of this section, you will be able to:
- Create a Todo web application with Bootstrap, React, and Node
- Create a Node back end
- Build the controller
- Set up the REST API
- Create the React front end
- Connect the React front end to the Node back end
In the previous section, you created a simple Todo application using Bootstrap and Django. In this section, you will continue to use Bootstrap to create another simple Todo application. But instead of working with Django, you will use React and Node. React, or React.js, is a JavaScript library popular to build user interfaces. Node, or Node.js, is a JavaScript runtime environment that provides users with the tools to develop web applications, as well as servers, scripts, and command-line tools.
Creating a Todo Web Application with Bootstrap and React and Node
When creating a Todo web application using React and Node, React serves as the front end, handling the user interface, as well as getting and setting data via HTTP requests using Axios. Node serves as the back end, using a REST API built with ExpressJS and the MongooseJS Object Data Modeling (ODM) to interact with a MongoDB database.
Prerequisites
To build the Todo application using Bootstrap, React, and Node, you will need the following software components: React v17.0.2, Bootstrap v4.5.0, Node v14.17.5, ExpressJS v4.17.2, MongooseJS v6.1.9, and Axios v0.21.0. To begin, download and install Node.
Creating the Node Back End
Several steps are needed to build the Node application back end required for the Todo application. This section will explain each of these steps.
Link to Learning
Node is a JavaScript runtime environment that provides users with the tools to develop web applications, as well as servers, scripts, and command-line tools. Node, which is free, is open-source and cross-platform. It was designed to develop network applications that are scalable, managing many connections simultaneously. Unlike the typical inefficient concurrency model, with Node, a callback is fired with each connection, and Node sleeps unless work needs to be done.
Creating the Node App
Before you can create the Todo application back end, you should create a Node app. To accomplish this, create the directory nodebackend/ and navigate into it. Next, run the following command to initialize the Node application:
$ npm init
After running this command, follow the prompt, which is highlighted in Figure 11.30.
After the Node application initialization is completed, a package.json file is generated, as shown in the following code.
{
"name": "nodebackend",
"version": "1.0.0",
"description": "Todo Web application with Bootstrap, ReactJS and NodeJS",
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"bootstrap",
"reactjs",
"nodejs",
"express",
"mongodb",
"rest",
"api"
],
"author": "",
"license": "ISC",
"dependencies": {
"body-parser": "^1.19.1",
"cors": "^2.8.5",
"express": "^4.17.2",
"mongoose": "^6.1.9"
}
}
Setting Up the Express Web Server
Once the Node application is initialized, the next step is to set up the Express web server. To do this, use the following command to install Express, Mongoose (Mongoose is a library for MongoDB that is used to interact with MongoDB by facilitating the modeling of data as objects), and other dependent packages in the nodebackend/ directory:
$ npm install express mongoose body-parser cors –save
Next, create the Express web server by going to the nodebackend/ directory, create the server.js file, and add the following code.
const express = require("express");
const bodyParser = require("body-parser");
const cors = require("cors");
const app = express();
var corsOptions = {
origin: "http://localhost:8081"
};
app.use(cors(corsOptions));
// parse requests of content-type - application/json
app.use(bodyParser.json());
// parse requests of content-type - application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: true }));
// routes
app.get("/", (req, res) => {
res.json({ message: "Welcome to the Todo Web App." });
});
require("./routes/todo.routes.js")(app);
// set port, listen for requests
const PORT = process.env.PORT || 8080;
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}.`);
});
Once you use the code to import Express, you can build the REST APIs. The body-parser package is used to create and parse the request object. The cors package is used to serve as middleware for Express that enables CORS. The Express web server will run on port 8080 as the default port. Use the following command to start the server:
$ node server.js
In a browser, navigate to http://localhost:8080/. The following page shown in Figure 11.31 renders.
Configuring the MongoDB Database and Mongoose
After the Express web server is created, the next step is to configure the MongoDB database and Mongoose. To do this, in the nodebackend/ directory, create the config/ directory. Then, in the config/ directory, create the file db.config.js and add the following code. This specifies the database connection URL to the MongoDB database.
module.exports = {
url: "mongodb://localhost:27017/todo_db"
};
Once the MongoDB connection URL is configured, the next step is to add code to connect to the database using Mongoose. To do this, in the nodebackend/ directory, create the models/ directory. In the models/ directory, create the index.js file and add the following.
const dbConfig = require("../config/db.config.js");
const mongoose = require("mongoose");
mongoose.Promise = global.Promise;
const db = {};
db.mongoose = mongoose;
db.url = dbConfig.url;
db.todos = require("./todo.model.js")(mongoose);
module.exports = db;
Next, the nodebackend/server.js file needs to be updated to enable the Express web server to establish a connection with the MongoDB. The code for this is shown in the following snippet.
const db = require("./models");
db.mongoose.connect(db.url, {
useNewUrlParser: true
}).then(() => {
console.log("Connected to the database successfully!")
}).catch(err => {
console.log("Cannot connect to the database: " , err);
process.exit();
});
Once the database connection code is completed, the next step is to create the Mongoose model. To do this, in the nodebackend/models/ directory, create the file todo.model.js and add the following code. This code defines a Mongoose schema for the todos model, which results in the creation of a todos collection in the MongoDB database.
module.exports = mongoose => {
var schema = mongoose.Schema({
title: {
type: String,
required: true,
},
content: {
type: String,
required: false,
},
created: {
type: String,
default: Date.now(),
required: true,
},
due_date: {
type: String,
required: true,
},
category: {
type: String,
required: true,
},
});
schema.method("toJSON", function() {
const { __v, _id, ...object } = this.toObject();
object.id = _id;
return object;
});
const Todos = mongoose.model("todos", schema);
return Todos;
};
Building the Controller
The next step is to build the controller. The controller contains code that calls the Mongoose CRUD functions to interact with the MongoDB database. To build the controller, in the nodebackend/ directory, create the controllers/ directory. Then, in the controllers/ directory, create the file todo.controller.js and add the code as shown. The code implements the CRUD functions create, findAll, findOne, update, delete, and deleteAll.
const db = require("../models");
const Todos = db.todos;
// Create and Save a new Todo item
exports.create = (req, res) => {
// Validate requred data
if (!req.body.title) {
res.status(400).send({ message: "Title can not be empty!" });
return;
}
if (!req.body.category) {
res.status(400).send({ message: "Category can not be empty!" });
return;
}
if (!req.body.due_date) {
res.status(400).send({ message: "Due date can not be empty!" });
return;
}
// Create a todo item
const todo = new Todos({
title: req.body.title,
content: req.body.content,
category: req.body.category,
due_date: req.body.due_date
});
// Save the todo item in the database
todo
.save(todo)
.then(data => {
res.send(data);
})
.catch(err => {
res.status(500).send({
message:
err.message || "An error occurred while creating the todo item."
});
});
};
// Retrieve all todo items
exports.findAll = (req, res) => {
const title = req.query.title;
var condition = title ? { title: { $regex: new RegExp(title), $options: "i" } } : {};
Todos.find(condition)
.then(data => {
res.send(data);
})
.catch(err => {
res.status(500).send({
message:
err.message || "An error occurred while retrieving todo items."
});
});
};
// Retrieve a todo item by id
exports.findOne = (req, res) => {
const id = req.params.id;
Todos.findById(id)
.then(data => {
if (!data)
res.status(404).send({ message: "Error finding todo item with id " + id });
else res.send(data);
})
.catch(err => {
res
.status(500)
.send({ message: "Error retrieving todo item with id=" + id });
});
};
// Update a todo item by id
exports.update = (req, res) => {
if (!req.body) {
return res.status(400).send({
message: "Specify a todo item to update. Todo item cannot be empty!"
});
}
const id = req.params.id;
Todos.findByIdAndUpdate(id, req.body, { useFindAndModify: false })
.then(data => {
if (!data) {
res.status(404).send({
message: `Cannot update todo item with id=${id}. Todo item was not found!`
});
} else res.send({ message: "The todo item was updated successfully." });
})
.catch(err => {
res.status(500).send({
message: "Error updating todo item with id=" + id
});
});
};
// Delete a todo item by id
exports.delete = (req, res) => {
const id = req.params.id;
Todos.findByIdAndRemove(id, { useFindAndModify: false })
.then(data => {
if (!data) {
res.status(404).send({
message: `Cannot delete todo item with id=${id}. Todo item was not found!`
});
} else {
res.send({
message: "The todo item was deleted successfully!"
});
}
})
.catch(err => {
res.status(500).send({
message: "Error deleting todo item with id=" + id
});
});
};
// Delete all todo items from the database
exports.deleteAll = (req, res) => {
Todos.deleteMany({})
.then(data => {
res.send({
message: `${data.deletedCount} All todo items were deleted successfully!`
});
})
.catch(err => {
res.status(500).send({
message:
err.message || "An error occurred while deleting all todo items."
});
});
};
Set Up the REST API
Once the controller is built, the next step is to set up the REST API. A client will send HTTP requests (e.g., GET, POST, PUT, and DELETE) to the REST API endpoints that determine how the server will manage these requests and provide a response. To do this, routes for the REST API are first defined. In the nodebackend/ directory, create the routes/ directory. In the routes/ directory, create the file todo.routes.js and add the following code. In this code, the routes require access to the CRUD functions declared in the controller.
module .exports = app => {
const todos = require("../controllers/todo.controller.js");
var router = require("express").Router();
// Create a new todo item
router.post("/", todos.create);
// Retrieve all todo items
router.get("/", todos.findAll);
// Retrieve a todo item by id
router.get("/:id", todos.findOne);
// Update a todo item by id
router.put("/:id", todos.update);
// Delete a todo item by id
router.delete("/:id", todos.delete);
// Delete all todo items
router.delete("/", todos.deleteAll);
app.use("/api/todos", router);
};
The next step is to update nodebackend/server.js to import the routes shown in the following code.
// routes
app.get("/", (req, res) => {
res.json({ message: "Welcome to the Todo Web App." });
});
require("./routes/todo.routes.js")(app);
// set port, listen for requests
const PORT = process.env.PORT || 8080;
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}.`);
});
The next step is to run the Express web server to test the CRUD functions and interact with the MongoDB database. To do this, run the following command:
$ node server.js
Link to Learning
As an API platform Postman can be a useful programming interface to build and use APIs. Postman is simplified, making it easier to build APIs quickly. With Postman, developers can also test and modify APIs to ensure they meet specifications.
To test the REST API, use Postman, which is an API platform testing tool that can be used as a client. To accomplish this, follow these steps:
- Install and launch Postman.
- Select POST as the request method.
- Enter the URL http://localhost:8080/api/todos to test creating a todo item.
- In the data input frame, select “Body.”
- Select “raw” and “JSON” for the data format.
- Enter a JSON object representing a todo item to be created as shown.
- Click Send.
After you follow these steps, the bottom frame should receive a response with status “200 OK” indicating the request was handled successfully. The body of the created todo item will also display along with a generated id field. You can use this method to test all the CRUD functions.
Creating the React Front End
Once you complete these steps and have the back end of the Todo web application up and running, the next step is to implement the front end using React. This section will outline the steps to accomplish this.
Link to Learning
React is a JavaScript library popular to build user interfaces. It relies on individual pieces known as components, which are JavaScript functions. React components are created using code and markup, and once created, React components can be combined to develop applications, screens, and web pages. React also has the capability to be added to HTML pages, rather than building an entire page using React.
Creating the React App
To create the React app, the first step is to run the following command. This should be done in a directory that is outside and separate from the nodebackend/ directory.
$ npx create-react-app reactfrontend
Once you run the command, it will generate the React application files in the reactfrontend/ directory, which is shown in Figure 11.32.
The next step is to navigate into the reactfrontend/ directory and launch the React application to confirm the React front-end application was created successfully. At this point, this application is not connecting to the Node back end, so it launches a default React page. Run the command, which will automatically launch a browser page to http://localhost:3000.
$ npm start
You will see the following, as illustrated in Figure 11.33.
Installing Bootstrap and Other Dependencies
The next step is to install the packages for bootstrap and reactstrap, which are necessary to use Bootstrap in a React app. To do this, in the reactfrontend/ directory, run the following commands:
$ npm install bootstrap@4.6.0 reactstrap@8.9.0 --legacy-peer-deps
$ npm install react-bootstrap
Next, import the Bootstrap4 CSS file into the React application. To do this, open reactfrontend/src/index.js and add the following Bootstrap import.
import React from 'react';
import ReactDOM from 'react-dom';
import 'bootstrap/dist/css/bootstrap.css';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
Create the React Components
The next step is to create the React components. The React user interface is rendered from the reactfrontend/src/App.js component. The reactfrontend/src/App.js file is generated when the React application is created. This is the main component of the React application, and all other components are added to the reactfrontend/src/App.js component. A few imports are added to the reactfrontend/src/App.js file, as shown in the following code. This includes an import for App.css. Customized CSS rules are added to reactfrontend/src/App.css.
//import logo from './logo.svg';
import './App.css';
import React, { Component } from "react";
import Modal from "./components/Modal";
import Nav from "./components/NavComponent";
import axios from "axios";
import { Button, Form, FormGroup, Label, Input } from 'reactstrap';
To compare with the Django web application in 11.2 Sample Responsive WAD with Bootstrap and Django, customized CSS rules were added to the style.css file in the static/ directory, as described in Creating the Templates.
Next, look at the following code, which shows App is a class component that extends React’s Component class. All state data is added to the this.state variable in the constructor.
class App extends Component {
constructor(props) {
super(props);
this.state = {
todoChecked: false,
todoList: [],
categories: [],
description: "",
modal: false,
activeItem: {
title: "",
description: "",
completed: false,
},
};
}
The next code snippet shows that every class component must include a render()
function. This function returns the components that construct the user interface for the Todo web application.
render () {
return(
<main>
<Nav />
<div className="container mt-5 pl-3">
<h1>Todo List</h1>
<Form>
<div className="inputContainer">
<FormGroup>
<Label htmlFor="description">Description</Label>
<Input type="text" id="description" name="description"
placeholder="Description"
value={this.state.description}
/>
</FormGroup>
</div>
<div className="inputContainer half last">
<FormGroup>
<Label htmlFor="category">Category</Label>
<Input id="category" className="taskCategory" type="select" name="category_select"
value={this.state.contactType}
>
<option className="disabled" value="">Choose a category</option>
<option>Work</option>
<option>Personal</option>
</Input>
</FormGroup>
</div>
<div className="inputContainer half last right">
<FormGroup>
<Label htmlFor="description">Due Date</Label>
<Input type="text" id="description" name="description"
placeholder="Due Date (mm/dd/yyyy)"
value={this.state.description}
/>
</FormGroup>
</div>
<div className="row">
<Button
className="taskAdd"
name="taskAdd" type="submit"
>
Edit
</Button>
<Button
className="taskDelete ml-1"
name="taskDelete"
type="submit"
onclick="$('input#sublist').click();"
>
Delete
</Button>
</div>
</Form>
<ul className="taskList">{this.renderItems()}</ul>
</div>
</main>
)
}
Connecting the React Front End to the Node Back End
The final step to create the Todo web application is to configure the React application so it can make requests to the API endpoints of the Node application. The React application uses Axios to fetch data by making requests to a given endpoint. To install Axios, run the following command in the reactfrontend/ directory:
$ npm install axios@0.21.1
Next, add a proxy to the Node application. The proxy will help tunnel API requests from the React application to http://localhost:8080 where the Node application will receive and handle the requests. To do this, open the reactfrontend/package.json file and add the following proxy.
{
"name": "reactfrontend",
"version": "0.1.0",
"private": true,
"proxy": "http://localhost:8080",
"dependencies": {
The next step is to create a service on the front end to send HTTP requests to the back end. This process uses Axios and is similar to how routes were created on the back-end side. The service will export CRUD functions and a finder method to interact with the MongoDB database. To do this, in the reactfrontend/ directory, create the services/ directory. Then, in the services/ directory, create the tile TodoService.js and add the following code.
import axios from "axios";
const getAll = () => {
return axios.get("/api/todos/");
};
const get = id => {
return axios.get(`/api/todos/${id}`);
};
const create = data => {
return axios.post("/api/todos", data);
};
const update = (id, data) => {
return axios.put(`/api/todos/${id}`, data);
};
const remove = id => {
return axios.delete(`/api/todos/${id}`);
};
const removeAll = () => {
return axios.delete(`/api/todos`);
};
export default {
getAll,
get,
create,
update,
remove,
removeAll
};
Finally, to complete this step, update reactfrontend/App.js to the following to call the services.
handleSubmit = (item) => {
var data = {
id: item.id,
title: item.title,
content: item.content,
due_date: item.due_date,
category: item.category
};
TodoService.create(data)
.then((res) => this.refreshList())
.catch((err) => console.log(err));
};
handleUpdate = (item) => {
var data = {
id: item.id,
title: item.title,
content: item.content,
due_date: item.due_date,
category: item.category
};
TodoService.update(item.id, data)
.then((res) => this.refreshList())
.catch((err) => console.log(err));
};
handleDelete = (item) => {
TodoService.remove(item.id)
.then((res) => this.refreshList())
.catch((err) => console.log(err));
};
refreshList = () => {
TodoService.getAll()
.then((res) => this.setState({ todoList: res.data }))
.catch((err) => console.log(err));
}
Once this is completed, run both the Express web server and the React app using the following commands:
$ node server.js
$ npm start
When this is done, use the form shown in Figure 11.34 to create a new todo item.
Footnotes
- 4Boostrap is a web content framework documented at getbootstrap.com.