Web applications in React, Bootstrap, MongoDB, Express/Printable version


Web applications in React, Bootstrap, MongoDB, Express

The current, editable version of this book is available in Wikibooks, the open-content textbooks collection, at
https://en.wikibooks.org/wiki/Web_applications_in_React,_Bootstrap,_MongoDB,_Express

Permission is granted to copy, distribute, and/or modify this document under the terms of the Creative Commons Attribution-ShareAlike 3.0 License.

Create a MongoDB database locally

  • 1) Install MongoDB Community Edition on your PC
 from here: https://docs.mongodb.com/manual/administration/install-community/
  • 2) Create a folder named mongo_database on proprio pc and a subfolder under it named data_items
  • 3) Open a terminal in the mongo_database folder and type:
mongod --dbpath = data_items --bind_ip 127.0.0.1

in this way the MongoDB server is started in the url 127.0.0.1 at port 27017

  • 4) Open another terminal in the mongo_database folder and type in sequence:
mongo
db
use db_items
db
exit

In this way the database we will use will be called: db_items

  • 5)Before starting the web server that we will create, the MongoDB server must be started with instruction 3)


Create a Node Express web server locally that connects to a MongoDB database

Installing Node.Js and Express Generator

edit
  • 1) Node.Js is a Javascript framework that also allows you to write server-side code and therefore, in particular, allows you to create an Express web server. npm (short for Node Package Manager [2]) is the default package manager for the Node.js JavaScript runtime environment. It consists of a command line client, also called npm, and an online database of public and private packages, called the npm registry.
  • 2) Install Node.js on your PC from here: https://nodejs.org/
  • 3) In the terminal type:
node -v
npm -v

to verify that both node and npm have been installed correctly on your system.

  • 4) Install the express-generator package via npm by typing in the terminal:
npm install express-generator -g
  • 5) Open the terminal in Documents and type:
express ItemsServer
  • 6) In the ItemsServer folder, type in the terminal:
npm install
  • 7) In the ItemsServer folder type in the terminal to start the server:
npm start
  • 8) In the browser at the link http://localhost:3000/ the message "Welcome to Express" will appear.A Node Express web server is done !

API REST and MONGOOSE

edit

The requests that the client makes to the server are made through the REST API. For example, if you are making a GET request, you are asking the server to return a resource. If you make a POST request, the server is being asked to create a new resource. If you are making a PUT request, you are asking the server to update an existing resource. And if you issue a DELETE request, the server is being asked to delete the resource identified by the particular URI. The exchange of data between client and server takes place in JSON format, which represents the standard with which Javascript objects are built. For example, the URL http://localhost:3000/items/234 means that the client asks the server to act on item 234, on which it is possible to perform GET, PUT or DELETE operations. To implement REST API calls on the MongoDB database you need to install mongoose:

  • 1) In the ItemsServer folder open a terminal and type:
npm install mongoose@5.7.0 –save
  • 2) Create a mongoose schema for the document items of the MongoDB database and insert it in the items.js file inside the models sub folder of the ItemServer folder
// items.js
var mongoose = require('mongoose');
var Schema = mongoose.Schema;

var itemSchema = new Schema({
    name: {
        type: String,
        required: true
    },
    quantity: {
        type: Number,
        required: true
    },
    measure: {
        type: String,
        required: true
    }
},    {
    timestamps: true
});

var Items = mongoose.model('Item', itemSchema);

module.exports = Items;
  • 3) In the subFolder routes of ItemServer, create the file itemRouter.js :
// itemRouter.js
const express = require('express');
const bodyParser = require('body-parser');
const mongoose = require('mongoose');

const Items = require('../models/items');

const itemRouter = express.Router();

itemRouter.use(bodyParser.json());

itemRouter.route('/')
.get((req,res,next) => {
    Items.find({})
    .then((resp) => {
        res.statusCode = 200;
        res.setHeader('Content-Type', 'application/json');
        res.json(resp);
    }, (err) => next(err))
    .catch((err) => next(err));
})
.post((req, res, next) => {
    Items.create(req.body)
    .then((resp) => {
        console.log('Item Created ', resp);
        res.statusCode = 200;
        res.setHeader('Content-Type', 'application/json');
        res.json(resp);
    }, (err) => next(err))
    .catch((err) => next(err));
})
.put((req, res, next) => {
    res.statusCode = 403;
    res.end('PUT operation not supported on /items');
})
.delete((req, res, next) => {
    Items.remove({})
    .then((resp) => {
        res.statusCode = 200;
        res.setHeader('Content-Type', 'application/json');
        res.json(resp);
    }, (err) => next(err))
    .catch((err) => next(err));    
});

itemRouter.route('/:itemId')
.get((req,res,next) => {
    Items.findById(req.params.itemId)
    .then((resp) => {
        res.statusCode = 200;
        res.setHeader('Content-Type', 'application/json');
        res.json(resp);
    }, (err) => next(err))
    .catch((err) => next(err));
})
.post((req, res, next) => {
    res.statusCode = 403;
    res.end('POST operation not supported on /items/'+ req.params.itemId);
})
.put((req, res, next) => {
    Items.findByIdAndUpdate(req.params.itemId, {
        $set: req.body
    }, { new: true })
    .then((resp) => {
        res.statusCode = 200;
        res.setHeader('Content-Type', 'application/json');
        res.json(resp);
    }, (err) => next(err))
    .catch((err) => next(err));
})
.delete((req, res, next) => {
    Items.findByIdAndRemove(req.params.itemId)
    .then((resp) => {
        res.statusCode = 200;
        res.setHeader('Content-Type', 'application/json');
        res.json(resp);
    }, (err) => next(err))
    .catch((err) => next(err));
});

module.exports = itemRouter;
  • 4) Edit the app.js file in the ItemsServer folder :
// app.js
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');

var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var itemsRouter = require('./routes/itemRouter');

var app = express();

const mongoose = require('mongoose');


mongoose.Promise = require('bluebird');

const url = 'mongodb://localhost:27017/db_items';

const options = {
  useMongoClient: true,
  autoIndex: false, // Don't build indexes
  reconnectTries: Number.MAX_VALUE, // Never stop trying to reconnect
  reconnectInterval: 500, // Reconnect every 500ms
  poolSize: 10, // Maintain up to 10 socket connections
  // If not connected, return errors immediately rather than waiting for reconnect
  bufferMaxEntries: 0
};

mongoose.connect(url, options);

app.use(function(req, res, next) {
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE");
  res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization");
  next();
  });

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');

app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));


app.use('/', indexRouter);
app.use('/users', usersRouter);
app.use('/items', itemsRouter);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  next(createError(404));
});

// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

module.exports = app;
  • 5) We created a Node Express web server that connects locally to a MongoDB database.


Use the Postman program to perform read, create, update, delete operations on a MongoDB database through a Node Express web server

  • 2) Start the MongoDB server, opening a terminal in the mongo_database folder and typing:
mongod --dbpath = data_items --bind_ip 127.0.0.1


  • 3) Start the Node Express web server, opening a terminal in the ItemsServer folder and typing:
npm start


  • 4) Open the Postman program and create a new item in the database via the POST method:
     
  • 5) Change the item via the PUT method and the item ID:
     
  • 6) View the data in the database via the GET method:
     
  • 7) Delete the item by the DELETE method and the item ID
     


Create a React app with Bootstrap that performs read, create, update, delete operations on a MongoDB database via a Node Express web server

Now we will create the Front end in React-Bootstrap that connects to the Node Express web server and through Rest API acts on the MongoDB database with GET, POST, PUT, DELETE operations on the items in a warehouse:

 
  • 1) In the Documents folder open a terminal and type:
npx create-react-app items-app


  • 2) In the items-app folder open a terminal and type to install the Bootstrap css framework:
npm install react-bootstrap bootstrap@5.1.3
  • 3) Replace the App.js file with the following:
//App.js
import React from 'react';
import 'bootstrap/dist/css/bootstrap.min.css';
import Items from './Componenti/Items';


class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
       baseUrl : 'http://localhost:3000/',
    };
   
  }
  render() {
    return (
      <div className="container-fluid">
        <title>Manage Items</title>

        <h1 className="text-center text-primary">Manage Items</h1>
        <h2 className="text-center text-secondary">with React, Bootstrap, MongoDB, Express</h2>

        <Items baseUrl={this.state.baseUrl} />


      </div>

    );
  }
}
export default App;


  • 4) Create a Components sub folder in which to insert the following files:
//items.js
import 'bootstrap/dist/css/bootstrap.min.css';
import Tabella from './Tabella';
import React from 'react';
import ModalView from './ModalView';

class Items extends React.Component
{
  constructor(props)
  {
    super(props);
    this.state = {
      inputID: "",
      inputName: "",
      inputQuantity: "",
      inputMeasure: "",
      show: false,
      message: ''
    };
    this.handleClickNuovo = this.handleClickNuovo.bind(this);
    this.handleClickSalva = this.handleClickSalva.bind(this);
    this.handleChangeName = this.handleChangeName.bind(this);
    this.handleChangeQuantity = this.handleChangeQuantity.bind(this);
    this.handleChangeMeasure = this.handleChangeMeasure.bind(this);
    this.handleClose = this.handleClose.bind(this);

  }
  
  handleChangeName(event)
  {
    this.setState({
      inputName: event.target.value,
    });
  }

  handleChangeQuantity(event)
  {
    this.setState({
      inputQuantity: event.target.value,
    });
  }

  handleChangeMeasure(event)
  {
    this.setState({
      inputMeasure: event.target.value,
    });
  }

  handleClickNuovo(event)
  {

    this.setState({
      inputID: "",
      inputName: "",
      inputQuantity: "",
      inputMeasure: "",
      show: false,
    });
  }

  handleClickSalva(event)
  {

    if (this.state.inputName === "" ||
      this.state.inputQuantity === "" ||
      this.state.inputMeasure === "" )
    {
      this.setState({
        show: true,
        message: 'Tutti i campi devono essere compilati! '
      })
    }

    else if (isNaN(parseFloat(this.state.inputQuantity)))
    {
      this.setState({
        show: true,
        message: 'Quantity is a number!'
      })
    } else
    {

      const requestOptions = {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ 
          name: this.state.inputName,
          quantity: this.state.inputQuantity,
          measure: this.state.inputMeasure
         })
    };

    fetch(this.props.baseUrl + 'items', requestOptions)
        .then(() => console.log('Item created'))
        .catch(err => {
          console.error(err);
        });
      
        this.setState({
          show: true,
          message: 'Item created!'
        })
     
    }
    
  }

  handleClose(event)
  {

    this.setState({
      show: false,
    });
  }

  render()
  {
    return (
      <div class="row">
        <div className="col-md-12">
          <div className="card" >
            <div className="card-body">
              <h5 className="card-title">Item:</h5>
              <div className="input-group mb-3">
              
                <span className="input-group-text">Item name:</span>
                <input

                  type="text"
                  className="form-control"
                  value={this.state.inputName}
                  onChange={this.handleChangeName}
                />
                <span className="input-group-text">Quantity:</span>
                <input

                  type="text"
                  className="form-control"
                  value={this.state.inputQuantity}
                  onChange={this.handleChangeQuantity}
                />
                <span className="input-group-text">Measure:</span>
                <input

                  type="text"
                  className="form-control"
                  value={this.state.inputMeasure}
                  onChange={this.handleChangeMeasure}
                />

              </div>


              <button onClick={this.handleClickNuovo} className="btn btn-primary">
                 New
              </button>
              <button onClick={this.handleClickSalva} className="btn btn-secondary">
                 Save
              </button>
              <button  className="btn btn-success">
                 Update
              </button>

              <ModalView
                message={this.state.message} show={this.state.show} handleClose={this.handleClose}
              />
            </div>
          </div>
          <Tabella baseUrl={this.props.baseUrl} />

        </div>

      </div>

    );
  }
}

export default Items;
//Tabella.js
import React from 'react';
import 'bootstrap/dist/css/bootstrap.min.css';
import ModalViewEdit from './ModalViewEdit';

class Tabella extends React.Component
{
  constructor(props)
  {
    super(props);
    this.state = {
      rows: [],
      show: false,
      editRow:{}
    }

    this.handleDelete = this.handleDelete.bind(this);
    this.handleEdit = this.handleEdit.bind(this);
    this.handleClose = this.handleClose.bind(this);
    this.handleClickEvent = this.handleClickEvent.bind(this);
    this.handleClick = this.handleClick.bind(this);

  }

  componentDidMount()
  {
    this.handleClick();
    document.addEventListener('click', this.handleClickEvent)
  }

  componentWillUnmount()
  {
    document.removeEventListener('click', this.handleClickEvent)
  }

  handleClickEvent(event)
  {
    this.handleClick();
  }

  handleClick()
  {
    fetch(this.props.baseUrl + 'items').then(function (data)
    {
      return data.json();
    }).then(json =>
    {
      this.setState({
        rows: json.map((row) =>
          <tr  >
            <th scope="row"  >{row._id}</th>
            <td>{row.name}</td>
            <td>{row.quantity}</td>
            <td>{row.measure}</td>
            <td ><button id={row._id} onClick={this.handleEdit} className="btn btn-info">
               Edit
            </button></td>
            <td ><button id={row._id} onClick={this.handleDelete} className="btn btn-danger">
               Delete
            </button></td>

          </tr>),

      });

    });

  }


  handleDelete = (e) =>
  {

    const key = e.currentTarget.getAttribute("id");

    fetch(this.props.baseUrl + 'items/' + key,
      {
        method: 'DELETE',

      })
      .then(() => console.log('Item deleted: ' + key))
      .catch(err =>
      {
        console.error(err);
      });

    this.handleClick();

  }

  handleEdit = (e) =>
  {
    const key = e.currentTarget.getAttribute("id");

    fetch(this.props.baseUrl + 'items/' +key).then(function (data)
    {
      return data.json();
    }).then(json =>
    {
      this.setState({
        editRow: json,
        show:true,
      });

    });
  }


  handleClose(event)
  {

    this.setState({
      show: false,
      editRow:{}
    });
  }

  render()
  {

    return (

      <>
        <table className="table table-striped" >
          <thead>
            <tr>
              <th scope="col">ID</th>
              <th scope="col">Name</th>
              <th scope="col">Quantity</th>
              <th scope="col">Measure</th>
            </tr>
          </thead>
          <tbody>
            {this.state.rows}
          </tbody>
        </table>
        
        {this.state.show && <ModalViewEdit
          baseUrl={this.props.baseUrl} editRow={this.state.editRow} show={this.state.show} handleClose={this.handleClose}
        />}
      </>
    );
  }
}
export default Tabella;


  • 5) In the terminal type:
npm start


and use the program.