Skip to ContentGo to accessibility pageKeyboard shortcuts menu
OpenStax Logo
Introduction to Computer Science

11.3 Sample Responsive WAD with Bootstrap/React and Node

Introduction to Computer Science11.3 Sample Responsive WAD with Bootstrap/React and Node

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.

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.

Screenshot of prompts for NodeJS.
Figure 11.30 This prompt appears when the Node application is initialized. (attribution: Copyright Rice University, OpenStax, under CC BY 4.0 license)

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.

Screenshot of start page of Express web server.
Figure 11.31 Once the Express web server is started, this page should appear at http://localhost:8080/. (rendered in Node; attribution: Copyright Rice University, OpenStax, under CC BY 4.0 license)

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

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.

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.

List of ReactJS application files in reactfrontend.
Figure 11.32 This shows the React application files in the reactfrontend/ directory. (rendered in React by Meta Open Source; attribution: Copyright Rice University, OpenStax, under CC BY 4.0 license)

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.

Screenshot of start page of ReactJS front-end application.
Figure 11.33 When the React front-end application is created successfully, this page launches at http://localhost:3000. (credit: React by Meta Open Source)

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.

Screenshot of Todo List form to create items.
Figure 11.34 Once the Todo web application is created using Bootstrap with React and Node, this form can be used to create todo items. (rendered using Bootstrap, under MIT license copyrighted 2018 Twitter, with React by Meta Open Source and Node; attribution: Copyright Rice University, OpenStax, under CC BY 4.0 license)

Footnotes

  • 4Boostrap is a web content framework documented at getbootstrap.com.
Citation/Attribution

This book may not be used in the training of large language models or otherwise be ingested into large language models or generative AI offerings without OpenStax's permission.

Want to cite, share, or modify this book? This book uses the Creative Commons Attribution License and you must attribute OpenStax.

Attribution information
  • If you are redistributing all or part of this book in a print format, then you must include on every physical page the following attribution:
    Access for free at https://openstax.org/books/introduction-computer-science/pages/1-introduction
  • If you are redistributing all or part of this book in a digital format, then you must include on every digital page view the following attribution:
    Access for free at https://openstax.org/books/introduction-computer-science/pages/1-introduction
Citation information

© Oct 29, 2024 OpenStax. Textbook content produced by OpenStax is licensed under a Creative Commons Attribution License . The OpenStax name, OpenStax logo, OpenStax book covers, OpenStax CNX name, and OpenStax CNX logo are not subject to the Creative Commons license and may not be reproduced without the prior and express written consent of Rice University.