MERN Stack Tutorial - Students-RFID-Featured-Image

Posted on

by

in

MERN Stack Tutorial – RFID Management System

Introduction

I am sharing with you the full-stack web application that I have created using the MongoDB-Express-React-Node (MERN) stack in this tutorial. In this post, I am creating a Students Radio Frequency Identification (RFID) Management system where we could execute a Create-Read-Update-Delete (CRUD) operation on our MongoDB database. I hope this tutorial would help you create your own application using the MERN technology stack as the use of Node.js and React greatly helps in the development process.

The steps outlined here are based on my research about using the MERN stack based on the available information on the internet so if you encounter any issues or see any room for improvement then just let me know. 🙂

What are we building?

We are creating a full-stack web application that displays the Student’s information including their RFID tags. The Student information is displayed as “cards” rather than in table format and you can execute CRUD operations on it. Our Student information is stored in our MongoDB Atlas database and we are using the MERN stack in executing CRUD operation in this tutorial.

MERN CRUD Tutorial - Students RFID Management System

We are also able to upload our profile images and saved them in our images folder. Also, we can scan thru our list of students by using client-side pagination.

If you want to see this project in a demo video then please see below.

Prerequisites

You should have a working knowledge of the following technologies:

  • Node.js
  • React.js
  • MongoDB
  • Express
  • HTML/CSS/Javascript

I used Visual Studio Code in developing this application and set up a free tier version of MongoDB Atlas to serve as my database. Visual Studio Code is an excellent IDE for any MERN Stack development and I have used it fully in this tutorial.

Also, you should have installed Node.js in your workstation.

Read Next:
Pico W -MicroPython MQTT – BMP/BME 280 Weather Station
Control DS18B20 using MicroPython with a Weather Station Project

Running the project

The code for this project is available for you to see in my GitHub repository. The steps on how to run it are in the README.md file. Follow everything there once you have set up your own MongoDB Atlas database cluster.

So that is all that is required and let us start building the project in this MERN Stack tutorial.

Create a root project directory

Create a directory where you would like to create your application

mkdir rfid-security-app

cd into the project folder. This will serve as the root directory of our project where we will create the backend and frontend applications.

cd rfid-security-app/

In this MERN stack tutorial, we will be programming the backend application and the frontend application separately.

Bootstrap backend server

We will first begin creating the backend server of our application using Node.js and Express. Several HTTP endpoints will be exposed in a REST interface format.

Setup the backend project

Create the directory where we will install our backend Node.js REST API Server

mkdir -p backend
cd backend

Initialize our backend folder using the command below. This will create the package.json files that will help us in maintaining our project.

npm init -y

Install the following dependencies that we are going to need.

npm install express mongoose dotenv multer cors path uuid

Create the index.js and add the following minimal code below. This will create an express server and start listening at port 5000.

const express = require("express");
const app = express();

app.listen("5000", () => {
  console.log("RFID backend API server is running.");
});

Run the project by executing the below command.

node index.js

Open your browser and then type in “localhost:5000”

Initial Node.js REST API Server

We now have our initial REST API Server running. How easy is that? We will improve this project as we go on.

Nodemon development setup

Nodemon is a tool that automatically restarts our development server whenever we change something in our files. This will help us a lot in the development phase as we don’t need to restart the server manually.

To install this package as a development dependency then execute the below command.

npm install nodemon --save-dev

Edit the package.json file and replace the scripts section with the following.

  "scripts": {
    "start": "nodemon index.js"
  },

Now start our REST API backend server by executing the below command.

npm start

You would see the following messages displayed on the console. Right now, whenever we do some changes to our project then our backend server will automatically restart to reflect our changes.

C:\git\rfid-security-app\backend>npm start

> backend@1.0.0 start C:\git\rfid-security-app\backend
> nodemon index.js

[nodemon] 2.0.20
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node index.js`
RFID backend API server is running.

Setup Middleware functions

Add cors support so that we could call our REST API from our frontend application.

const express = require("express");
const cors = require("cors");
const app = express();

//setup cors for our project
app.use(cors());

app.listen("5000", () => {
  console.log("RFID backend API server is running.");
});

We will also add serving static files from our server and to do that just add the following lines of code.

const express = require("express");
const cors = require("cors");
const path = require("path");

const app = express();
//setup cors for our project
app.use(cors());
//load static files
app.use("/images", express.static(path.join(__dirname, "/images")));

app.listen("5000", () => {
  console.log("RFID backend API server is running.");
});

We can now access our image files by typing this in our browser URL.

http://localhost:5000/images/defaultPic.png

Note: This file is not yet present in your project so this may result in an error when you put this in your browser. We will create this file later in the steps.

Connect to our MongoDB Atlas database

Before that, let’s first place our connection string in a “.env” so that it will not be included in our code and our GitHub repository. This connection string will be loaded thru the dotenv package. Create a .env file in the root of your project. Replace the username and password including the connection string to your MongoDB Atlas cluster.

MONGO_DB_URL=mongodb+srv://<USERNAME>:<PASSWORD>@iotdb.e5gtqlx.mongodb.net/rfiddb?retryWrites=true&w=majority

Edit your index.js and add the following lines of code.

const express = require("express");
const mongoose = require("mongoose");
const cors = require("cors");
const path = require("path");
const dotenv = require("dotenv");

const app = express();

//setup cors for our project
app.use(cors());
//load static files
app.use("/images", express.static(path.join(__dirname, "/images")));

//load the .env file
dotenv.config();
//parse the json request and put it in the req.body
app.use(express.json());

//connect to our mongodb atlas database
mongoose
  .connect(process.env.MONGO_DB_URL)

//start the server
app.listen("5000", () => {
  console.log("RFID backend API server is running.");
});

If no error is displayed in the Visual Studio Code terminal then it means that we were able to connect to our MongoDB Atlas cluster.

Setup our Mongoose Schema

Create a new folder called model and inside it create a new file called Student.js. Inside Student.js, we will create the schema of our project. This will represent the details of our students.

const mongoose = require("mongoose");

const StudentSchema = new mongoose.Schema({
    studentId: {
        type: String,
        required: true,
        unique: true
    },
    firstName: String,
    lastName: String,
    course: String,
    address: String,
    rfidBadgeNumber: {
        type: String,
        unique: true
    },
    imagePic: String
});


module.exports = mongoose.model("Student", StudentSchema);

Create routes for our project

We will set up next the routes needed for our application and to do that let us create a folder called routes and create a file student.js inside it.

The following are the REST API endpoints that we are going to need in our project.

HTTP MethodHTTP EndpointDescription
POSThttp://localhost:5000/api/studentsCreate Student
GETlocalhost:5000/api/students?rfid=123456&studentId=2022-01138Get all students list or get
student by RFID badge
number or studentId
GETlocalhost:5000/api/students/:idGet student by ObjectId
PUTlocalhost:5000/api/studentsUpdate student info
DELETElocalhost:5000/api/studentsDelete student

We will bootstrap our routes by listing first all the needed API routes.

const router = require("express").Router();
const Student = require("../model/Student")

// Create Student 

// Get Student list or Search Student by rfid or studentid query parameters

// Get Student by ID

// Update Student

// Delete Student

module.exports = router;

Edit your index.js file also and load these routes.

const express = require("express");
const app = express();
const cors = require("cors");
const path = require("path");
const mongoose = require("mongoose");
const dotenv = require("dotenv");
const studentRoute = require("./routes/students");
  
//setup cors for our project
app.use(cors());
//load static files
app.use("/images", express.static(path.join(__dirname, "/images")));

//load the .env file
dotenv.config();
//parse the json request and put it in the req.body
app.use(express.json());

//connect to our mongodb atlas database
mongoose
  .connect(process.env.MONGO_DB_URL)

//load our rest api routes
app.use("/api/students", studentRoute);

//start the server
app.listen("5000", () => {
  console.log("RFID backend API server is running.");
});

Create also the images folder where we will place the profile images of our Students. At the end of this step, your backend project should now look like the following.

Node.js API Backend Folder Structure

Create a Student

In order for us to handle the profile image upload of our students then we will be using multer package to handle the “multipart/form-data” upload. Add the following lines of code in your student.js route. I have commented on each line for your reference.

const router = require("express").Router();
const Student = require("../model/Student");
const multer = require("multer");
const path = require("path");
const { v4: uuidv4 } = require("uuid");
const fs = require('fs');

// Upload profile image directory
const IMAGE_DIR = "./images/";

// set multer disk storage
const storage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, IMAGE_DIR);
  },
  filename: (req, file, cb) => {
    //generate random uuid
    const fileName = uuidv4() + path.extname(file.originalname);
    cb(null, fileName);
  },
});

// Limit file upload only to images
const upload = multer({
  storage: storage,
  fileFilter: (req, file, cb) => {
    if (
      file.mimetype == "image/png" ||
      file.mimetype == "image/jpg" ||
      file.mimetype == "image/jpeg"
    ) {
      cb(null, true);
    } else {
      cb(null, false);
      return cb(new Error("Only .png, .jpg and .jpeg format is allowed!"));
    }
  },
});

// Create Student
router.post("/", upload.single("file"), async (req, res) => {
  const newStudent = new Student(req.body);
  try {
    // save the generated filename in our MongoDB Atlas database
    newStudent.imagePic = req.file.path;
    const savedStudent = await newStudent.save();
    res.status(200).json(savedStudent);
  } catch (error) {
    res.status(500).json({ error: error });
  }
});

Test: Create a Student

We will use the Postman application in testing our REST API Endpoints.

Related Content:
Install Postman Windows

Open your Postman application and place the following entries.

Postman - Create Student

If no error is encountered then the following data should be available in our MongoDB Atlas database.

MongoDB Atlas - Create Student

Also, the profile image that we have uploaded should be placed in our images folder.

Visual Studio Code - Image Upload

Get Student List or Search Student by RFID or StudentId

To get the list of students or search students by RFID or StudentId then we can add the following route in our student.js route.

// Get Student list or Search Student by rfid or studentid query parameters
router.get("/", async (req, res) => {
  const studentId = req.query.studentId;
  const rfId = req.query.rfId;

  // if either studenId or rfId query parameters is present
  if (studentId || rfId) {
    try {
      let student;
      if (studentId && rfId) {
        student = await Student.find({
          studentId: studentId,
          rfidBadgeNumber: rfId,
        });
      } else if (studentId) {
        student = await Student.find({ studentId });
      } else if (rfId) {
        student = await Student.find({ rfidBadgeNumber: rfId });
      }
      return res.status(200).json(student);
    } catch (error) {
      return res.status(500).json({ error: error });
    }
  }
  // else return the whole Student list
  try {
    const studentList = await Student.find();
    res.status(200).json(studentList);
  } catch (error) {
    res.status(500).json({ error: error });
  }
});

Test: Get Student List

Using Postman, issue a GET request at the following endpoint

localhost:5000/api/students
Postman - Get All Students

Test: Search Student by RFID or StudentId

To search for a student by either RFID badge number or studentId then we can pass it in as query parameters like this:

localhost:5000/api/students?studentId=2022-01138
Postman - Search Student

Get Student By Id (ObjectId)

To get Student information by passing the MongoDB automatically assigned ObjectID then we can add the following code.

router.get("/:id", async (req,res) =>{
  try {
    const student = await Student.findById(req.params.id)
    res.status(200).json(student);
  } catch (error) {
    res.status(500).json({ error: error });
  }
})

Test: Get Student By Id (ObjectId)

Using Postman, we can send in the following parameters.

Postman - Get Student by ObjectId

Update Student

To update student information then we would need the following code.

// Update Student
router.put("/:id", upload.single("file"), async (req, res, next) => {
  //If a new profile pic is uploaded then process it first by deleting the old image file from disk
  if (req.file) {
    try {
      //find by id
      const oldStudentDetails = await Student.findById(req.params.id);
      if (!oldStudentDetails) {
        throw new Error("Student not found!");
      }

      //if old image file exist then the delete file from directory
      if (fs.existsSync(oldStudentDetails.imagePic)) {
        fs.unlink(oldStudentDetails.imagePic, (err) => {
          if (err) {
            throw new Error("Failed to delete file..");
          } else {
            console.log("file deleted");
          }
        });
      }
    } catch (error) {
      res.status(500).json({ error: error });
    }
  }
  // Update the database with new details
  try {
    const updatedStudent = await Student.findByIdAndUpdate(
      req.params.id,
      {
        $set: req.body,
        imagePic: req.file?.path,
      },
      { new: true }
    );
    res.status(200).json(updatedStudent);
  } catch (error) {
    res.status(500).json({ error: error });
  }
});

Test: Update Student

Send the following parameters in Postman.

Postman -Update Student

Delete Student

Perform delete by adding the following code.

// Delete Student
router.delete("/:id", async (req, res) => {
  try {
    await Student.findByIdAndDelete(req.params.id);
    res.status(200).json("Student has been deleted...");
  } catch (error) {
    res.status(500).json(error);
  }
});

Test: Delete Student

Using Postman then we can execute the following test.

Postman - Delete Student

That is all there is for the backend part of our application. We have coded the REST API endpoints that will handle the interaction with our database. All these endpoints are called by the frontend application that we will be creating next.

The next section of this MERN stack tutorial will feature how we could create the user interface part of our project using React.js

Bootstrap frontend server

Now that we have created our backend application then we could start working on the frontend side of our application. To do that, go into the root directory and execute the below command. This might take some time so please be patient.

npx create-react-app frontend

Now that we have bootstrapped the front end and the back end of our application then we will continue programming this in Visual Studio Code. To open up this project in VS Code enter the following code.

code .

We will be using the functional style of coding of our React components and pages in this post.

Install the dependencies for our frontend project

npm install axios react-router-dom

We will use Axios when calling our backend REST API endpoint and the react-router-dom for routing our pages.

Cleanup the project folder

The “create-react-app” creates several files we won’t need at the moment, so we will clean up everything first.

In the public folder, delete everything except the index.html file. We will clean up also the index.html file so that it will just look like this by removing all the comments.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <meta
      name="description"
      content="RFID Application"
    />
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.0/css/all.min.css" integrity="sha512-xh6O/CkQoPOWDdYTDqeRdPCVd1SpvCA9XXcUnZS2FmJNp1coAFzvtCN9BmamE+4aHK8yyUHUSCcJHgXloTyT2A==" crossorigin="anonymous" referrerpolicy="no-referrer" />
    <link href="https://fonts.googleapis.com/css2?family=Anton&family=Josefin+Sans:wght@100;200;300;500;600;700&family=Roboto:wght@400;700&display=swap" rel="stylesheet">
    <title>RFID Application</title>
    <style>
    </style>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
  </body>
</html>

I have added a link to the font-awesome CDN and downloaded some fonts from Google fonts as well.

In the src folder, delete everything except the index.js and the App.js file.

We will clean up the index.js file also by editing it and making it look like this.

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

The App.js will now look like this also.

function App() {
  return (
    <div className="App">
      Students RFID App
    </div>
  );
}

export default App;

At the end of this step, your project should look like this.

RFID React App

You can now run the application by executing the following commands.

npm start

Create the pages

I will need 4 pages for our project.

PageDescription
/homeHome Page
/editEdit Page
/addAdd Page
/deleteDelete Page

Create a folder called page and create the following files. I have created specific css files for styling purposes for each page.

RFID React App - Pages

You can initialize each page file by doing the following.

import React from 'react'
import "./home.css"

export default function Home() {
  return (
    <div>Home Page</div>
  )
}

Setup the router

We can now edit the App.js to set up the routing of our pages using the following code. This will set up each path to the pages that we have created above. Our root page will render the Home page.

import Home from "./pages/home/Home";
import Edit from "./pages/edit/Edit";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Delete from "./pages/delete/Delete";
import Add from "./pages/add/Add";

function App() {
  return (
    <main className="App">
      <BrowserRouter>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/add" element={<Add />} />
          <Route path="/edit/:id" element={<Edit />} />
          <Route path="/delete/:id" element={<Delete />} />
        </Routes>
      </BrowserRouter>
    </main>
  );
}

export default App;

Setup the components

The power of React is the ability to divide part of your pages into components so I have thought of doing the following divisions in this post. We will create a separate React Components for each division.

Full Stack MERN CRUD Tutorial - COMPONENTS

Create the React components/pages

In your src folder, create a folder called components and create the following files respectively. Each of these *.jsx files is a functional React component that represents each division of our user pages.

The Message component is just used to display information or error messages to the user.

RFID React App - Components

The example code for the “Header.jsx” is like below. We will code each part of the components in the succeeding sections.

import React from "react";
import "./header.css";

export default function Header() {
  return (
    <div className="projectHeader">
      <h1>Students RFID System</h1>
    </div>
  );
}

Let us start coding our pages.

Home page

The Home page contains the entry point of our project. It contains the other components that we have outlined above including the QueryFilter, the Cards, and the Pagination component.

The pagination logic that I have implemented here is taken from the following post which uses client-side pagination. As mentioned in that article, this may not be optimal if the number of records grows.

It calls the REST API endpoint that retrieves the list of students during the initial load by using the useEffect.

import React, { useState, useEffect } from "react";
import QueryFilter from "../../components/filter/QueryFilter";
import Pagination from "../../components/pagination/Pagination";
import Cards from "../../components/cards/Cards";
import axios from "axios";
import "./home.css";
import Header from "../../components/header/Header";

export default function Home() {
  // state variables
  const [students, setStudents] = useState([]);
  const [loading, setLoading] = useState(true);
  const [currentPage, setCurrentPage] = useState(1);
  const [recordsPerPage] = useState(12);

  // Pagination logic
  const indexOfLastRecord = currentPage * recordsPerPage;
  const indexOfFirstRecord = indexOfLastRecord - recordsPerPage;
  const currentRecords = students.slice(indexOfFirstRecord, indexOfLastRecord);
  const nPages = Math.ceil(students.length / recordsPerPage);

  // Get Students on initial load
  useEffect(() => {
    getStudents();
  }, []);

  const getStudents = async () => {
    const res = await axios.get("http://localhost:5000/api/students");
    setStudents(res.data);
    setLoading(false);
  };

  // function called to search for student
  const searchStudent = async (studentId, rfId) => {
    let url;
    if (studentId && rfId) {
      url = `http://localhost:5000/api/students?studentId=${studentId}&rfId=${rfId}`;
    } else if (studentId) {
      url = `http://localhost:5000/api/students?studentId=${studentId}`;
    } else if (rfId) {
      url = `http://localhost:5000/api/students?rfId=${rfId}`;
    }
    const res = await axios.get(url);
    setStudents(res.data);
  };

  // the jsx code that contains our components
  return (
    <section className="main">
      {loading && <div>Loading page....</div>}
      <Header />
      <QueryFilter searchStudent={searchStudent} getStudents={getStudents} />
      <Cards students={currentRecords} />
      <Pagination
        nPages={nPages}
        currentPage={currentPage}
        setCurrentPage={setCurrentPage}
      />
    </section>
  );
}

The styling for our page is in the home.css file and I would not be diving so much into this as this is purely CSS.

.main{
  display: flex;
  flex-direction: column;
  width: 100%;
}

Add student page

The Add.jsx page contains the logic that will add student information to our database. The profile image picture is passed into the Axios using programmatic Formdata. The styling for this page is powered by the add.css file. You can check out the file in my GitHub repository if you want to know how it is set up.

This page is called when the user clicks the Add Student in the query filter component. I have added several comments for each line of code for you to understand what it does.

import axios from "axios";
import React, { useState } from "react";
import { useNavigate } from "react-router-dom";
import Header from "../../components/header/Header";
import Message from "../../components/message/Message";
import "./add.css";

export default function Add() {
  // For navigation during button click
  const navigate = useNavigate();
  // State object of our student
  const [student, setStudent] = useState({
    studentId: "",
    firstName: "",
    lastName: "",
    course: "",
    address: "",
    rfidBadgeNumber: "",
    imagePic: "",
  });

  // represents the profile picture uploaded
  const [file, setFile] = useState(null);
  const [message, setMessage] = useState({
    show: false,
    msg: "",
    type: "",
  });

  // Used for updating our state object
  const updateStudent = (e) => {
    const fieldName = e.target.name;
    setStudent((currentStudent) => ({
      ...currentStudent,
      [fieldName]: e.target.value,
    }));
  };

  // Show info or error message during calling of the Axios REST API
  const showMessage = (show = false, type = "", msg = "") => {
    setMessage({ show, type, msg });
  };

  // Handle form submit and using FormData API
  const handleSubmit = async (e) => {
    e.preventDefault();
    const studenData = new FormData();
    studenData.append("studentId", student.studentId);
    studenData.append("firstName", student.firstName);
    studenData.append("lastName", student.lastName);
    studenData.append("course", student.course);
    studenData.append("address", student.address);
    studenData.append("rfidBadgeNumber", student.rfidBadgeNumber);
    if (file) {
      studenData.append("file", file);
    }
    try {
      await axios.post("http://localhost:5000/api/students", studenData);
      showMessage(true, "info", "Successfully added student information");
    } catch (error) {
      showMessage(true, "error", error);
    }
  };

  // Displays the form for Adding
  return (
    <>
      <Header />
      <div className="header">
        <h1>Add Student</h1>
      </div>
      <section className="managePage">
        <form className="editForm" onSubmit={handleSubmit}>
          <div className="fields">
            <div className="imgColumn">
              <img
                src={
                  file
                    ? URL.createObjectURL(file)
                    : "http://localhost:5000/images/defaultPic.png"
                }
                alt="Profile Pic"
              />
              <label htmlFor="fileInput" className="fileUploadLabel">
                <i className="fa-solid fa-circle-plus addProfileIcon">
                  Add Profile Pic
                </i>
              </label>
              <input
                type="file"
                id="fileInput"
                onChange={(e) => setFile(e.target.files[0])}
                style={{ display: "none" }}
              />
            </div>
            <div className="fieldsColumn">
              <div className="fieldRow">
                <label htmlFor="studentId" className="fieldLabel">
                  Student ID
                </label>
                <input
                  type="text"
                  name="studentId"
                  id="studentId"
                  value={student.studentId}
                  onChange={updateStudent}
                  className="addInputs"
                />
              </div>
              <div className="fieldRow">
                <label htmlFor="firstName" className="fieldLabel">
                  First Name
                </label>
                <input
                  type="text"
                  name="firstName"
                  id="firstName"
                  value={student.firstName}
                  onChange={updateStudent}
                  className="addInputs"
                />
              </div>
              <div className="fieldRow">
                <label htmlFor="lastName" className="fieldLabel">
                  Last Name
                </label>
                <input
                  type="text"
                  name="lastName"
                  id="lastName"
                  value={student.lastName}
                  onChange={updateStudent}
                  className="addInputs"
                />
              </div>
              <div className="fieldRow">
                <label htmlFor="course" className="fieldLabel">
                  Course
                </label>
                <input
                  type="text"
                  name="course"
                  id="course"
                  value={student.course}
                  onChange={updateStudent}
                  className="addInputs"
                />
              </div>
              <div className="fieldRow">
                <label htmlFor="address" className="fieldLabel">
                  Address
                </label>
                <input
                  type="text"
                  name="address"
                  id="address"
                  value={student.address}
                  onChange={updateStudent}
                  className="addInputs"
                />
              </div>
              <div className="fieldRow">
                <label htmlFor="rfidBadgeNumber" className="fieldLabel">
                  RFID Badge Number
                </label>
                <input
                  type="text"
                  name="rfidBadgeNumber"
                  id="rfidBadgeNumber"
                  value={student.rfidBadgeNumber}
                  onChange={updateStudent}
                  className="addInputs"
                />
              </div>
            </div>
          </div>

          <div className="btnContainer">
            <button type="submit" className="bottomButton">
              Add
            </button>
            <button
              type="button"
              className="bottomButton"
              onClick={() => navigate("/")}
            >
              Back
            </button>
          </div>
          <div>
            {message.show && (
              <Message {...message} removeMessage={showMessage} />
            )}
          </div>
        </form>
      </section>
    </>
  );
}

Edit student

The Edit.jsx page contains the logic on how you can edit Student information and call the necessary backend REST API. The edit.css does the styling for this page.

import axios from "axios";
import React from "react";
import { useState, useEffect } from "react";
import { useNavigate, useParams } from "react-router-dom";
import "./edit.css";
import Message from "../../components/message/Message";
import Header from "../../components/header/Header";

export default function Edit() {
  // For navigation during button click
  const navigate = useNavigate();
  // Extract the ID from the browser url
  const { id } = useParams();
  // Our student state information
  const [student, setStudent] = useState({
    studentId: "",
    firstName: "",
    lastName: "",
    course: "",
    address: "",
    rfidBadgeNumber: "",
    imagePic: "",
  });
  // The profile picture file
  const [file, setFile] = useState(null);
  // Messages used to display if successful or error during updating 
  const [message, setMessage] = useState({
    show: false,
    msg: "",
    type: "",
  });

  // Get the student information by passing the ID into our MongoDB Atlas database
  useEffect(() => {
    const getStudent = async () => {
      const res = await axios.get("http://localhost:5000/api/students/" + id);
      setStudent(res.data);
    };
    getStudent();
  }, []);

  // Update our state object
  const updateStudent = (e) => {
    const fieldName = e.target.name;
    setStudent((currentStudent) => ({
      ...currentStudent,
      [fieldName]: e.target.value,
    }));
  };

  // Function to show or hide messages
  const showMessage = (show = false, type = "", msg = "") => {
    setMessage({ show, type, msg });
  };

  // Handle form submit
  const handleSubmit = async (e) => {
    e.preventDefault();
    const studenData = new FormData();
    studenData.append("studentId", student.studentId);
    studenData.append("firstName", student.firstName);
    studenData.append("lastName", student.lastName);
    studenData.append("course", student.course);
    studenData.append("address", student.address);
    studenData.append("rfidBadgeNumber", student.rfidBadgeNumber);
    if (file) {
      studenData.append("file", file);
    }
    try {
      await axios.put(
        "http://localhost:5000/api/students/" + student._id,
        studenData
      );
      showMessage(true, "info", "Successfully edited student information");
    } catch (error) {
      showMessage(true, "error", error);
    }
  };

  // The user interface for the Edit page
  return (
    <>
      <Header />
      <div className="header">
        <h1>Edit Student</h1>
      </div>
      <section className="managePage">
        <form className="editForm" onSubmit={handleSubmit}>
          <div className="fields">
            <div className="imgColumn">
              <img
                src={
                  file
                    ? URL.createObjectURL(file)
                    : student.imagePic
                    ? `http://localhost:5000/${student.imagePic}`
                    : "http://localhost:5000/images/defaultPic.png"
                }
                alt="Profile Pic"
              />
              <label htmlFor="fileInput" className="fileUploadLabel">
                <i className="fa-solid fa-circle-plus addProfileIcon"></i>Add
                Profile Pic
              </label>
              <input
                type="file"
                id="fileInput"
                onChange={(e) => setFile(e.target.files[0])}
                style={{ display: "none" }}
              />
            </div>
            <div className="fieldsColumn">
              <div className="fieldRow">
                <label htmlFor="studentId" className="fieldLabel">
                  Student ID
                </label>
                <input
                  type="text"
                  name="studentId"
                  id="studentId"
                  value={student.studentId}
                  onChange={updateStudent}
                  className="editInputs"
                />
              </div>
              <div className="fieldRow">
                <label htmlFor="firstName" className="fieldLabel">
                  First Name
                </label>
                <input
                  type="text"
                  name="firstName"
                  id="firstName"
                  value={student.firstName}
                  onChange={updateStudent}
                  className="editInputs"
                />
              </div>
              <div className="fieldRow">
                <label htmlFor="lastName" className="fieldLabel">
                  Last Name
                </label>
                <input
                  type="text"
                  name="lastName"
                  id="lastName"
                  value={student.lastName}
                  onChange={updateStudent}
                  className="editInputs"
                />
              </div>
              <div className="fieldRow">
                <label htmlFor="course" className="fieldLabel">
                  Course
                </label>
                <input
                  type="text"
                  name="course"
                  id="course"
                  value={student.course}
                  onChange={updateStudent}
                  className="editInputs"
                />
              </div>
              <div className="fieldRow">
                <label htmlFor="address" className="fieldLabel">
                  Address
                </label>
                <input
                  type="text"
                  name="address"
                  id="address"
                  value={student.address}
                  onChange={updateStudent}
                  className="editInputs"
                />
              </div>
              <div className="fieldRow">
                <label htmlFor="rfidBadgeNumber" className="fieldLabel">
                  RFID Badge Number
                </label>
                <input
                  type="text"
                  name="rfidBadgeNumber"
                  id="rfidBadgeNumber"
                  value={student.rfidBadgeNumber}
                  onChange={updateStudent}
                  className="editInputs"
                />
              </div>
            </div>
          </div>

          <div className="btnContainer">
            <button type="submit" className="bottomButton">
              Edit
            </button>
            <button
              type="button"
              className="bottomButton"
              onClick={() => navigate("/")}
            >
              Back
            </button>
          </div>
          <div>
            {message.show && (
              <Message {...message} removeMessage={showMessage} />
            )}
          </div>
        </form>
      </section>
    </>
  );
}

Delete student

The “Delete.jsx” page handles the deletion of student information.

import axios from "axios";
import React from "react";
import { useState, useEffect } from "react";
import { useNavigate, useParams } from "react-router-dom";
import "./delete.css";
import Message from "../../components/message/Message";
import Header from "../../components/header/Header";

export default function Delete() {
  // For navigation during button click
  const navigate = useNavigate();
  // Extract the ID from the browser url
  const { id } = useParams();
  // Our student state information
  const [student, setStudent] = useState({
    studentId: "",
    firstName: "",
    lastName: "",
    course: "",
    address: "",
    rfidBadgeNumber: "",
    imagePic: "",
  });

  const [message, setMessage] = useState({
    show: false,
    msg: "",
    type: "",
  });

  // Get the student information by passing the ID into our MongoDB Atlas database
  useEffect(() => {
    const getStudent = async () => {
      const res = await axios.get("http://localhost:5000/api/students/" + id);
      setStudent(res.data);
    };
    getStudent();
  }, []);

  // Function to show or hide messages
  const showMessage = (show = false, type = "", msg = "") => {
    setMessage({ show, type, msg });
  };

  // Handle form submit
  const handleSubmit = async (e) => {
    e.preventDefault();
    try {
      await axios.delete("http://localhost:5000/api/students/" + student._id);
      showMessage(true, "info", "Successfully deleted student information");
      clearStudentInfo();
    } catch (error) {
      showMessage(true, "error", error);
    }
  };

  // Clear student info after deletion
  const clearStudentInfo = () => {
    setStudent({
      studentId: "",
      firstName: "",
      lastName: "",
      course: "",
      address: "",
      rfidBadgeNumber: "",
      imagePic: "",
    });
  };

  // The user interface for the Delete page
  return (
    <>
      <Header />
      <div className="header">
        <h1>Delete Student</h1>
      </div>
      <section className="managePage">
        <form className="editForm" onSubmit={handleSubmit}>
          <div className="fields">
            <div className="imgColumn">
              <img
                src={
                  student.imagePic
                    ? `http://localhost:5000/${student.imagePic}`
                    : "http://localhost:5000/images/defaultPic.png"
                }
                alt="Profile Pic"
              />
            </div>
            <div className="fieldsColumn">
              <div className="fieldRow">
                <label htmlFor="studentId" className="fieldLabel">
                  Student ID
                </label>
                <input
                  type="text"
                  name="studentId"
                  id="studentId"
                  value={student.studentId}
                  readOnly={true}
                  className="deleteInputs"
                />
              </div>
              <div className="fieldRow">
                <label htmlFor="firstName" className="fieldLabel">
                  First Name
                </label>
                <input
                  type="text"
                  name="firstName"
                  id="firstName"
                  value={student.firstName}
                  readOnly={true}
                  className="deleteInputs"
                />
              </div>
              <div className="fieldRow">
                <label htmlFor="lastName" className="fieldLabel">
                  Last Name
                </label>
                <input
                  type="text"
                  name="lastName"
                  id="lastName"
                  value={student.lastName}
                  readOnly={true}
                  className="deleteInputs"
                />
              </div>
              <div className="fieldRow">
                <label htmlFor="course" className="fieldLabel">
                  Course
                </label>
                <input
                  type="text"
                  name="course"
                  id="course"
                  value={student.course}
                  readOnly={true}
                  className="deleteInputs"
                />
              </div>
              <div className="fieldRow">
                <label htmlFor="address" className="fieldLabel">
                  Address
                </label>
                <input
                  type="text"
                  name="address"
                  id="address"
                  value={student.address}
                  readOnly={true}
                  className="deleteInputs"
                />
              </div>
              <div className="fieldRow">
                <label htmlFor="rfidBadgeNumber" className="fieldLabel">
                  RFID Badge Number
                </label>
                <input
                  type="text"
                  name="rfidBadgeNumber"
                  id="rfidBadgeNumber"
                  value={student.rfidBadgeNumber}
                  readOnly={true}
                  className="deleteInputs"
                />
              </div>
            </div>
          </div>

          <div className="btnContainer">
            <button type="submit" className="bottomButton">
              Delete
            </button>
            <button
              type="button"
              className="bottomButton"
              onClick={() => navigate("/")}
            >
              Back
            </button>
          </div>
          <div>
            {message.show && (
              <Message {...message} removeMessage={showMessage} />
            )}
          </div>
        </form>
      </section>
    </>
  );
}

Card display of student Information

The Card.jsx component handles the display of user information in a card-like manner. It is configured to display at least 12 student information per page. It uses Grid CSS Styling to display the student information.

You can check out the card.css on how this is styled.

import React from "react";
import "./cards.css";
import { Link } from "react-router-dom";

export default function Cards({ students }) {
  return (
    <div className="cardsWrapper">
      <div className="cards">
        {students.length === 0 && <p>No student(s) found</p>}
        {students.map((student) => {
          return (
            <div key={student._id} className="card">
              <img
                src={
                  student.imagePic
                    ? "http://localhost:5000/" + student.imagePic
                    : "http://localhost:5000/images/defaultPic.png"
                }
                alt="profile pic"
              />
              <h3>{`${student.firstName} ${student.lastName}`}</h3>
              <div className="text">
                <p>
                  <span className="label">Student ID:</span>
                </p>
                <p>
                  <span className="info">{student.studentId}</span>
                </p>
                <p>
                  <span className="label">Course:</span>
                </p>
                <p>
                  <span className="info">{student.course}</span>
                </p>
                <p>
                  <span className="label">RFID Number:</span>
                </p>
                <p>
                  <span className="info">{student.rfidBadgeNumber}</span>
                </p>
              </div>
              <div className="btnContainer">
                <Link to={`edit/${student._id}`} className="cardBtn m-top">
                  Edit
                </Link>
                <Link to={`delete/${student._id}`} className="cardBtn m-top">
                  Delete
                </Link>
              </div>
            </div>
          );
        })}
      </div>
    </div>
  );
}

Filter students by Student ID or RFID tag

The QueryFilter.jsx component displays search boxes where you can filter the list either by Student Id or by RFID tag or both. It calls the REST API endpoint that we created earlier by passing the query parameters when we call the REST API endpoint thru Axios.

import React from "react";
import { useState } from "react";
import { useNavigate } from "react-router-dom";
import "./queryfilter.css";

export default function QueryFilter({ searchStudent, getStudents }) {
  // State information for the filter by StudentId or RFID or both
  const [studentId, setStudentId] = useState("");
  const [rfid, setRfId] = useState("");
  // For page navigation during button click
  const navigate = useNavigate();

  // Clear the input text
  const clearSearch = () => {
    setStudentId("");
    setRfId("");
    getStudents();
  };

  // Display the filter jsx
  return (
    <div className="filter">
      <div className="filterFields">
        <label htmlFor="studentId" className="filterLabel">
          Student ID
        </label>
        <input
          name="studentId"
          className="filterInputs"
          type="text"
          placeholder="Enter Student ID"
          value={studentId}
          onChange={(e) => setStudentId(e.target.value)}
        />
      </div>
      <div className="filterFields">
        <label htmlFor="rfid" className="filterLabel">
          RFID Number
        </label>
        <input
          name="rfid"
          className="filterInputs"
          type="text"
          placeholder="Enter RFID"
          value={rfid}
          onChange={(e) => setRfId(e.target.value)}
        />
      </div>
      <div className="filterFields">
        <div className="btn-container">
          <button
            type="button"
            className="queryBtn"
            onClick={() => searchStudent(studentId, rfid)}
          >
            Search Student
          </button>
          <button type="button" className="queryBtn" onClick={clearSearch}>
            Clear Search
          </button>
          <button
            type="button"
            className="queryBtn"
            onClick={() => navigate("/add")}
          >
            Add Student
          </button>
        </div>
      </div>
    </div>
  );
}

Pagination Component

The Pagination.jsx handles the pagination of our Students list. The logic is taken completely from this post.

import React from "react";
import "./pagination.css";

export default function Pagination({ nPages, currentPage, setCurrentPage }) {
  // Display the numbers in between
  const pageNumbers = [...Array(nPages + 1).keys()].slice(1);

  // Set the page number when next page is clicked
  const nextPage = () => {
    if (currentPage !== nPages) setCurrentPage(currentPage + 1);
  };

  // Set the page number when previous page is clicked
  const prevPage = () => {
    if (currentPage !== 1) setCurrentPage(currentPage - 1);
  };

  // Pagination buttons
  return (
    <div className="pagination-container">
      <i
        className="fa-solid fa-backward-step"
        id="prev-button"
        aria-label="Previous page"
        title="Previous page"
        onClick={prevPage}
      ></i>
      {pageNumbers.map((pgNumber, index) => {
        return (
          <button
            key={index}
            className={`pagination-number ${
              currentPage === pgNumber ? "active" : ""
            } `}
            onClick={() => setCurrentPage(pgNumber)}
          >
            {pgNumber}
          </button>
        );
      })}

      <i
        className="fa-solid fa-forward-step"
        id="next-button"
        aria-label="Next page"
        title="Next page"
        onClick={nextPage}
      ></i>
    </div>
  );
}

Display message information

The Message.jsx shows the display of successful or in-error information whenever we execute CRUD operations. It automatically closes itself upon display after several seconds.

import React from "react";
import { useEffect } from "react";
import "./message.css"

export default function Message({ msg, type, removeMessage }) {
  useEffect(() => {
    const timeout = setTimeout(() => {
      removeMessage();
    }, 2000);
    return () => clearTimeout(timeout);
  }, []);
  return <p className={`message message-${type}`}>{msg}</p>;
}

Display heading for our project

The Header.jsx displays the title of our project.

import React from "react";
import "./header.css";

export default function Header() {
  return (
    <div className="projectHeader">
      <h1>Students RFID System</h1>
    </div>
  );
}

That is all for all the pages and components in our web application using the MERN Stack in this tutorial

Wrap up

In this MERN StackTutorial, I have shown you the steps on how I was able to create my own full-stack web application using Node.js and React, and using MongoDB Atlas to store the information. We have executed CRUD operations on our database and applied pagination of our data.

I hope you have learned something! Happy exploring!

If you like my post then please consider sharing this. Thanks!

5 responses to “MERN Stack Tutorial – RFID Management System”

  1. Raspberry Pi RFID Door Lock System with Database

    […] Related Content: MERN Stack Tutorial – RFID Management System […]

  2. randall Avatar
    randall

    just stumbled across your page – thank you so much for all of those tutorials, there must have gone an awful lot of time into them.

    I am using a raspberry Pi 4B with the latest Raspian, installed Node.js via the nodeRED script (therefore including fresh npm that I since updated and everything should be as fresh as can be).

    After a lot of troubleshooting concerning the .env file I decided to run the single operations from the html-tutorial. This wouldnt work aswell so I decided to hardcode the connection string, which finally worked. (adding a semicolon there and a line of “mongoose.set(‘strictQuery’, false);” in front did relieve some errors)

    you used model/Student.js and routes/student.js, where the later was called students.js on your git.

    After getting the backend running, I was able to connect to the page from another pc within my network. I couldnt get postman to post the data you propose (as postman is not availabe for ARM architecture, I used it from a windows machine, close to the scenario I want to use. It throws me an error 500 that is explained no further.

    I used your git code again, hardcoded the connection string. This would show the picture (in contrast to the code I transferred via this html tutorial, albeit the file is in the correct folder) and also, it is able to push the MOCK_DATA.json into the db. still, I cannot post the data via postman.

    What would be the recommended method to authenticate in postman against the api in order for to post? I think I tried them all but there must be some setting I didnt find anything about.
    Kind regards

    1. donsky Avatar
      donsky

      I did not add any authentication mechanism in that post so there is no need to authenticate in Postman so that is weird.
      You don’t need Postman when you are in Raspberry Pi as you can test the API using curl commands from within the Raspberry Pi

      The next thing to check, are you able to ping the Raspberry Pi from your PC?

      Thanks for the kind words and happy exploring!

  3. randall Avatar
    randall

    Thank you for your comprehensive collection of tutorials!
    Have you tried this on a Raspberry Pi?
    I had trouble with the .env file and had to hardcode the connection string.
    After this, as there is no ARM version of Postman, I couldnt bring Postman from a separate windows PC to POST, but only to GET data from the Pi running your code.
    Also, the webpage-update function wouldnt want to update the mongoDB database after having successfully pushed the MOCK_DATA to the db as described within your git.
    the GET would show within the raspberry browser but not within the windows browser within the same network. I would be glad if your wanted to correspond with me as to how one would troubleshoot this.
    Thanks in advance,
    kind regards

    1. donsky Avatar
      donsky

      Hey, I am leaning toward a Node.js environment problem with your Raspberry Pi.
      I cannot exactly pinpoint the issue as I don’t have your current setup.
      I mostly used a Windows PC Environment in developing this post.

Leave a Reply

Your email address will not be published. Required fields are marked *