MongoDB: MongoDB 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:
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 1const 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:
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.
No comments:
Post a Comment