Creating Web Applications using MERN Stack

MongoDBMongoDB is a cross-platform document-oriented database program. It is a NoSQL Database. The data is stored in JSON document format and this database can store images in Base64 encoded format. More information on MongoDB can be read from this link.


Express.js: This is a Web Application Framework for Node.js. We can use this for building REST APIs and also for building Web Applications. More information on Express.js can be read from this link.

React.js makes it painless to create modern front-end applications. This library provides an easy approach for building interactive views. 

Node.js: This is an open-source, cross-platform JavaScript runtime environment used to run JavaScript code out-of-browser. We use Node.js for server-side JavaScript execution. More information can be read from this link.

The Component is a major building block of a React.js application. Component is the object that contains data, logic and user interface (UI) for the View. We can build an application with complex Views  using React.js components. 

In JavaScript full stack applications, we can use JavaScript object model for End-to-End apps that varies  from UI to Database. We use MongoDB, Express.js, React.js and Node.js for designing such applications, which are collectively also knows as MERN apps. In this article, we will build a complete JavaScript application based on MERN a.k.a the MERN stack.   

       

This article covers a React.js application that uploads an image file to Express.js REST APIs. The REST API further stores this file in MongoDB in Base64 encoded format. The following figure explains an idea of the application implementation



Figure 1: The  MERN Application

The application is developed using Visual Studio Code (VSCode). This is an open-source,cross-platform IDE by Microsoft and can be downloaded from this link. We also need Node.js which can be downloaded from this link as well as MongoDB which can be downloaded from this link. This will install MongoDB server and the MongoDB Compass. This is an IDE for creating Database and collections in MongoDB.

Creating a MERN Application 

Step 1:  Create a folder of the name nodejs-file-uploader-react. Navigate to this folder from the Node.js command prompt. Since we will be creating a React.js application using create-react-app CLI, run the following command from the command prompt:

npm install -g create-react-app

This will install the CLI. We can use it for creating React.js application. Run the following command prompt from nodejs-file-uploader-react path to create a project of the name same as the folder name
create-react-app .

This command will create a react application with necessary packages. 

Step 2: In the React application, add a new folder and name it as servercode. This folder will contains the Node.js and Express.js server-side logic.

In the React application, add a new folder of the name fileStore. This folder will store uploaded files from the React client application. Since we will be uploading image files from React.js and upload it to Express.js REST APIs and further store the file to MongoDB database, we need to install following packages: 

  • mongoose, to connect to MongoDB and create collection schema to store documents in the collections.
  • express, to create REST APIs
  • multer, to received uploaded files to REST APIs as multi-part form data
  • body-parser, to read the JSON data posted by the client application to Express REST APIs
  • cors, to manage cross-origin-resource-sharing from server side
  • axios, to manage Http requests from the React.js client application
  • concurrently, to run Express.js REST APIs server app and React.js client app at a time. 

  To install these packages run the following command:

npm install --save mongoose express multer body-parser cors axios concurrently

The Server Side Code

Step 3: In the servercode folder, add a new file and name it as schema.js. Add the code in this file as shown in the listing 1

const mongoose = require('mongoose');
// line to avoid error of model overwrite
var StudentSchema = mongoose.Schema;

var Student = new StudentSchema({
    StudentId: Number,
    StudentName: String,
    CourseName: String,
    UniversityName: String,
    AdmissionYear: Number,
    FileName: String,
    File: Object,
    MimeType: String
});

module.exports = mongoose.model('Student', Student, 'Student');

Listing 1: The Student Schema

The above code shows the mongoose schema definition. This schema will be used to create a collection in the MongoDB database.

Step 4: In the servercode folder, add a new JavaScript file and name this file as dal.js. This file contains logic to connect to the MongoDB database and write data in it which is received from the React.js application. Add the code in the file as shown in the listing 2:

const fs = require('fs');
const mongoose = require('mongoose');

var StudentModel = require('./schema');
class DataStorage {
  
    uploadStudentData = (request, response) => {
            // 1. connect to the MongoDB database
            let db = mongoose.connect("mongodb://localhost/Students", {
                useNewUrlParser: true, // Parse Schema and Map with Model
                useUnifiedTopology: true
            });
            // 2. make sure that connection is established
            let dbConnection = mongoose.connection; // make sure that the connection is done 
            if (!dbConnection) {
                console.log('Cannot Connect to the database');
                return;
            }

            console.log('In Upload Image Method' + request.file.originalname);
            // 3. read the received image
            const image = fs.readFileSync(request.file.path);
            // 4. encode the image in base64
            const encodedImage = image.toString('base64');
            // 5. the JSON object to store file informatin on MongoDB collection

            let std = {
                StudentId: request.body.StudentId,
                StudentName: request.body.StudentName,
                CourseName: request.body.CourseName,
                UniversityName: request.body.UniversityName,
                AdmissionYear: request.body.AdmissionYear,
                FileName: request.file.originalname,
                File: new Buffer(encodedImage, 'base64'),
                MimeType: request.file.mimetype
            };
            // 6. create a enw document
            StudentModel.create(std, (err, res) => {
                if (err) {
                    response.send({ statusCode: 500, message: err.message });
                }
                console.log(`Success ${res._id}`);
                response.send({ statusCode: 200, data: `Success ${res._id}` });
            })
        }
        // method to read stuudent data from the database
    getStudentData = (request, response) => {
        let db = mongoose.connect("mongodb://localhost/Students", {
            useNewUrlParser: true, // Parse Schema and Map with Model
            useUnifiedTopology: true
        }); 
        let dbConnection = mongoose.connection; // make sure that the connection is done 
        if (!dbConnection) {
            console.log('Cannot Connect to the database');
            return;
        }
        StudentModel.find((err, res) => {
            if (err) {
                response.send({ statusCode: 500, message: err.message });
            }
            response.send({ statusCode: 200, data: res });
        });

    }
}
module.exports = DataStorage;

Listing 2: The DataAccess logic

The above code shows the DataStorage class. This class contains uploadStudentData() method. This method contains code to connect to MongoDB database.

The method accepts HTTP request and response parameters from the Express instance. Since this method is responsible to store  Student data along with the uploaded student image in MongoDB, it uses the fs module to read the file and its originalname (the posted file name).  This file is then encoded in Base64 encoding.

The method further reads the posted data from the HTTP request body and stores the data in a JSON object. Using the StudentModel schema of mongoose object, the JSON object is stored in to MongoDB collection by creating a new JSON document in it. Once the JSON document is created successfully, the HTTP status code 200 response is generated along with the id of the newly created document.

The DataStorage class also contains the getStudentData() method. This method also accepts HTTP request and response objects from the Express instance. This method contains code to connect to MongoDB database and read all students data and respond back to the client.

Step 5: In the servercode folder, add a new file and name it as server.js.  Add the code in this file as shown in the listing 3

const express = require('express');
const multer = require('multer');
const bodyParser = require('body-parser');
const cors = require('cors');
const dal = require('./dal');

const dalObj = new dal();
const expressInstance = express();
expressInstance.use(cors());
expressInstance.use(bodyParser.urlencoded({ extended: true }));

// the disk storage
let diskStorage = multer.diskStorage({
    destination: (request, file, callback) => {
        callback(null, 'fileStore');
    },
    filename: (request, file, callback) => {
        callback(null, file.originalname);
    }
});


// the multer object
let uploadObject = multer({ storage: diskStorage });

expressInstance.post('/uploadStudent', uploadObject.single('file'), dalObj.uploadStudentData);
expressInstance.get('/getStudents', dalObj.getStudentData);

expressInstance.listen(4500, () => {
    console.log('listening on 4500')
});

Listing 3: The Server Side code

The above code contains logic for using Express instance and multer to manage HTTP communication to REST APIs created using Express. The multer object is used to configure the local file storage (in our case it is a fileStore folder) to store HTTP uploaded images from the client application.

The Express instance uses body-parser and cors middleware to parse HTTP posted data from the client application and allows to access HTTP requests from the JavaScript client applications respectively. The milter.dishStorage() method is used to configure the local folder for storing the HTTP Posted files from the client applications. The uploadObject instance of the type multer is used to read the file from the folder so that it can be used to save in a MongoDB Collection. The get() and post() methods of the Express Instance are used to call getStudentData() and uploadStudentData() methods from the DataStorage class. The Express REST APIs are hosted and published from port 4500.

This completes our server side coding. You can test these rest APIs using Postman.

The React.js Front-End Code

Step 6: In the src folder of the react application, add a new folder and name it as models. In this folder add a new file and name it as student.js. Add the code for constant arrays in the file as shown in the listing 4.

export const Universities = [
    "AMV", "PNQ", "NGP", "MUM"
];
export const Courses = [
    "ENGG", "COMP", "MEDI"
];

Listing 4: The Student Constants for Universities and Courses

Step 7: In the src folder, add a new file and name it as StudentComponent.jsx. Add the code in this file as shown in listing 5

import React, { Component } from 'react';
import { Student, Universities, Courses } from './models/student';
import axios from 'axios';
class StudentComponent extends Component {
    constructor(props) {
        super(props);
        this.state = {
            StudentId:0,
            StudentName: '',
            CourseName: '',
            UniversityName:'',
            AdmissionYear:0,
            ImageUrl:'',
             universities:Universities,
            courses:Courses,
            students:[],
            column :['_id','StudentId', 'StudentName',
            'CourseName','UniversityName','AdmissionYear', 'FileName']
        }
    }
    
    handleInputChange=(evt)=>{
        this.setState({[evt.target.name]:evt.target.value});
    }

    selectUploadFileHandler = (event)=>{
        //1. define the array for the file type e.g. png, jpeg
        const fileTypes = ['image/png', 'image/jpeg'];
 
        // 2. get the file type
         let file = event.target.files;
         console.log(`File ${file}`);
        // 3. the message for error if the file type of not matched
        let errMessage = [];
        // 4. to check the file type to match with the fileTypes array iterate 
        // through the types array
        if(fileTypes.every(extension=> file[0].type !==extension)){
            errMessage.push(`The file ${file.type} extension is not supported`);
        } else {
            this.setState({
                ImageUrl: file[0]
            });
        }
     };

    save=()=> {
         // 1. the FormData object that contains the data to be posted to the 
        // WEB API
        alert(this.state.ImageUrl);
        const formData = new FormData();
        formData.append('file', this.state.ImageUrl);
        formData.append('StudentId', this.state.StudentId);
        formData.append('StudentName', this.state.StudentName);
        formData.append('CourseName', this.state.CourseName);
        formData.append('UniversityName', this.state.UniversityName);
        formData.append('AdmissionYear', this.state.AdmissionYear);
        
        // 2. post the file to the WEB API
        axios.post("http://localhost:4500/uploadStudent", formData, {
      onUploadProgress: progressEvent => {
        this.setState({
          progress: (progressEvent.loaded / progressEvent.total*100)
        })
      }
    })
      .then((response) => { 
        this.setState({status:`upload success ${response.data}`});
        console.log(`upload success ${response.data}`);
        this.loadData();
      })
      .catch((error) => { 
        this.setState({status:`upload failed ${error}`});
      })
    }
    clear=()=> {
        this.setState({StudentId:0});
        this.setState({StudentName:''});
        this.setState({CourseName:''});
        this.setState({UniversityName:''});
        this.setState({AdmissionYear:0});
        this.setState({ImageUrl:''});
    }
    componentDidMount=()=>{
      this.loadData();
    }
    loadData=()=>{
      axios.get('http://localhost:4500/getStudents').then((resp) => {
        this.setState({students: resp.data.data});
       // let f =  resp.data.data[0].File.substr(4).split('//')[0];
       let f =  resp.data.data[0].File;

        console.log(f);
     },(error) => {
       console.log(`Error Occured ${error}`);
     });
    }
   render() {
        return (  
             <div className="container">
             <form name="Student">
                <div className="form-group">
                  <label htmlFor="StudentId">Student Id</label>
                  <input type="text" 
onChange={this.handleInputChange.bind(this)} name="StudentId" 
value={this.state.StudentId} className="form-control"/>
                </div>
                <div className="form-group">
                 <label htmlFor="StudentName">Student Name</label>
                 <input type="text" 
onChange={this.handleInputChange.bind(this)} name="StudentName"
 value={this.state.StudentName} className="form-control"/>
                </div>
                <div className="form-group">
                 <label htmlFor="CourseName">Course Name</label>
                 <select className="form-control" 
onChange={this.handleInputChange.bind(this)} 
name="CourseName" value={this.state.CourseName}>
                    <option>Select Course Name</option>
                    {
                        this.state.courses.map((c,i) =>(
                            <option key={i} value={c}>{c}</option>
                        ))
                    }
                 </select>
                </div>
                <div className="form-group">
                 <label htmlFor="UniversityName">University Name</label>
                 <select className="form-control" 
onChange={this.handleInputChange.bind(this)} 
name="UniversityName" value={this.state.UniversityName}>
                    <option>Select University Name</option>
                    {
                       this.state.universities.map((u,i) =>(
                           <option key={i} value={u}>{u}</option>
                       ))
                   }
                 </select>
                </div>
               
                <div className="form-group">
                 <label htmlFor="ImageUrl">Image Url</label>
                 <input type="file" name="ImageUrl" 
onChange={this.selectUploadFileHandler.bind(this)}
 className="form-control" />
                </div>
                <div className="form-group">
                  <input type="button" className="btn btn-warning" 
onClick={this.clear.bind(this)} value="Clear"/>
                  <input type="button" className="btn btn-success" 
onClick={this.save.bind(this)} value="Save"/>
                </div>
                </form>
                <br/>
                <div className="container">
                    <table className="table table-bordered table-striped">
                      <thead>
                        <tr>
                          {
                            this.state.column.map((h,i)=>(
                              <th key={i}>{h}</th>
                            ))
                          }
                        </tr>
                      </thead>
                      <tbody>
                        {
                          this.state.students.map((s,i) => (
                            <tr key={i}>
                             {
                              <td>{s._id}</td>
                             }
                             {
                              <td>{s.StudentId}</td>
                             }
                             {
                              <td>{s.StudentName}</td>
                             }
                             {
                              <td>{s.CourseName}</td>
                             }
                             {
                              <td>{s.UniversityName}</td>
                             }
                             {
                               <td></td>
                             }
                             {
                              <td>
                              { 
                                <img src={`data:image/jpeg;base64,${s.File}`}  
style={{width: 100, height: 100}} alt="data"></img>
                              }
                              </td>
                             }
                            </tr>  
                          ))
                        }
                      </tbody>
                    </table>
                </div>
            </div>
       );
    }
}

export default StudentComponent;

Listing 5: The Component 

The code in the  above listing shows the react component.

The component defines state properties for accepting Student information from the end-user. The handleInputChange() method reads data entered in each HTML input text  element. The selectUploadFileHandler() method contains code for reading an extension of the selected file selected by the end-user to upload.

If the file is not a png or jpeg, it will not be accepted, else the selected file URL will be assigned to the ImageUrl state property of the component. The save() method uses FormData object to define form fields for storing data entered by end-user in HTML elements and the selected file for uploading. The method further posts the FormData to REST API using post() method of the axios object.

The clear() method clears all input elements on UI. The componentDidMount() method calls the loadData() method. The loadData() method makes HTTP get call to REST API and receive students data. The received students data is stored in students state property of the component. The render() method contains UI for the component. The input text elements are bound to the state properties like StudentId, StudentName.

We also have select elements. These elements generate options using courses and universities arrays. The input type file is bound with the ImageUrl state property to provide file selection for the end-user to upload file. The table at the bottom on the render() method will generate rows and columns based on the students array. The image element will be generated in the last column of the table that will show the student image received from the REST API.

Step 8: Modify the index.js and import StudentCompopnent in it and render this component using ReactDOM object as shown in the listing 6:
......
import StudentComponent from './StudentComponent';

ReactDOM.render(  , document.getElementById('root'));
......
Listing 6: The index.js code to render the StudentComponenty     

Step 8: Modify the package.json to run the React and Node REST Applications concurrently as shown in the  listing 7

 "server": "node servercode/server.js",
        "start": "concurrently \"npm run server\" \"npm run client\""

Listing 7: package.json modification 

Use the Command prompt as explained in Step 1 and run the following command to run the application

npm run start

This command will start REST  API and React applications. The REST API will be hosted and published from port 4500 and the React application will be open in the browser using http://localhost:3000 URL as shown in the following figure:

Figure 2: The React application

Enter student information and click on the Save button, the data will be posted to REST API and saved in the MongoDB Collection and then the student data will be received from the REST API and  displayed in Table as shown in the following figure


Figure 3: The Result

You can see the data uploaded in the MongoDB Collection using MongoDB Compass as shown in Figure 4.


Figure 4: The data in the collection

Conclusion: Node with Express provides an easy mechanism to design and develop REST APIs using JavaScript. Using MongoDB database we can store the JSON documents in a collection along with the images in Base64 encoding. Using React.js we can easily design and develop views. So MongoDB, Express, React and Node (MERN) provides a complete JavaScript stack to design and develop great apps. 





About The Author

Mahesh Sabnis is a Microsoft MVP having over 18 years of experience in IT education and development. He is a Microsoft Certified Trainer (MCT) since 2005 and has conducted various Corporate Training programs for .NET Technologies (all versions). He also blogs regularly at DotNetCurry.com. Follow him on twitter @maheshdotnet

No comments: