DEV Community

Cover image for Making First Simple CRUD App with MERN-Stack
Gerwin Jo
Gerwin Jo

Posted on • Edited on

Making First Simple CRUD App with MERN-Stack

Today, I learn about making simple CRUD student app by using MERN Stack. MERN can be abbreviated with MongoDB, Express, React and Node.JS. MERN also can be called a full stack which include frontend and backend.

Results

  • List All Students
    Alt Text

  • Create Students
    Alt Text

You can check and run full repository on my GitHub: https://github.com/gerwinjonathan/school-app

Overview

  • MongoDB: General purpose, document-based, distributed database and popular database.
  • Express: Minimalist framework built for Node.js
  • React: UI framework designed for web application.
  • Node.JS: JavaScript runtime built for scalable application.

Preparation

First things First

  1. Make sure Node.JS is installed.
    Node.JS: https://nodejs.org/en/download/

  2. Install React App with NPX.

npx create-react-app school-app

  1. Install all package manager those are built for this project.

npm install mongoose express bootstrap axios reactstrap react-icons react-router-dom react-dom --save

* mongoose: tools for object modelling built for Node.JS (https://mongoosejs.com/) * reactstrap: Bootstrap CSS for React (https://reactstrap.github.io/) * react-icons: Icons for react (https://react-icons.github.io/react-icons/) * axios: Promise based HTTP client for the browser and node.js 
Enter fullscreen mode Exit fullscreen mode

Let's get Started - FrontEnd ⚡

  • Prepare your pages 📄

There are 4 pages those we want to build. Put it into components folder in src folder. Those are create-student.component, delete-student.component, edit-student.component, list-student.component.

  • create-Student.component.jsx
import React, { useState, useEffect } from 'react'; import { Form, FormGroup, Label, Input, Col, Button } from 'reactstrap'; import { AiOutlineUserAdd, AiOutlineUser, AiOutlineExport, AiOutlineForward } from 'react-icons/ai'; import axios from 'axios'; const CreateStudent = (props) => { const [data, setData] = useState({ student_name: "", student_address: "", student_number: "", student_entry: "", student_year: "", student_verification: false }); const onChangeStudentData = (e) => { setData({ ...data, [e.target.name]: e.target.value }) } const onSubmitStudentData = (e) => { e.preventDefault(); axios.post('http://localhost:4000/all_student/add', data).then(res => console.log(res.data)); setData({ student_name: "", student_address: "", student_number: "", student_entry: "", student_year: "", student_verification: false }); } return ( <div style={{ marginTop: 10 }}> <h3><AiOutlineUserAdd /> Create Student</h3> <Form onSubmit={onSubmitStudentData}> <FormGroup row> <Col> <Label><AiOutlineUser /> Student Name </Label> <Input type="text" name="student_name" className="form-control" value={data.student_name} onChange={onChangeStudentData} /> </Col> </FormGroup> <FormGroup row> <Col> <Label><AiOutlineExport /> Address </Label> <Input type="text" name="student_address" className="form-control" value={data.student_address} onChange={onChangeStudentData} /> </Col> </FormGroup> <FormGroup row> <Col> <Label><AiOutlineExport /> Student Number </Label> <Input type="number" name="student_number" className="form-control" value={data.student_number} onChange={onChangeStudentData} /> </Col> </FormGroup> <FormGroup row> <Col md={6}> <Label><AiOutlineExport /> Entry Level </Label> <Input type="text" name="student_entry" className="form-control" value={data.student_entry} onChange={onChangeStudentData} /> </Col> <Col md={6}> <Label><AiOutlineExport /> Entry Year </Label> <Input type="number" name="student_year" className="form-control" value={data.student_year} onChange={onChangeStudentData} /> </Col> </FormGroup> <Button color="primary"><AiOutlineForward /> Submit</Button> </Form> </div> ); } export default CreateStudent; 
Enter fullscreen mode Exit fullscreen mode
  • delete-student.component.jsx
import React, { useState, useEffect } from 'react'; import { Form, FormGroup, Label, Input, Col, Button } from 'reactstrap'; import { AiOutlineUser, AiOutlineExport, AiOutlineDelete } from 'react-icons/ai'; import axios from 'axios'; const DeleteStudent = (props) => { const [data, setData] = useState({ student_name: "", student_address: "", student_number: "", student_entry: "", student_year: "", student_verification: false }); useEffect(() => { const fetchData = async () => { const result = await axios( `http://localhost:4000/all_student/${props.match.params.id}` ); setData({ ...result.data }); }; fetchData(); }, []); const onDeleteStudentData = (e) => { e.preventDefault(); axios.delete(`http://localhost:4000/all_student/delete/${props.match.params.id}`, data).then(res => console.log(res.data)); props.history.push('/'); } return ( <div style={{ marginTop: 10 }}> <h3>Delete Student</h3> <Form onSubmit={onDeleteStudentData}> <FormGroup row> <Col> <Label><AiOutlineUser /> Student Name </Label> <Input readOnly type="text" name="student_name" className="form-control" value={data.student_name} /> </Col> </FormGroup> <FormGroup row> <Col> <Label><AiOutlineExport /> Address </Label> <Input readOnly type="text" name="student_address" className="form-control" value={data.student_address} /> </Col> </FormGroup> <FormGroup row> <Col> <Label><AiOutlineExport /> Student Number </Label> <Input readOnly type="number" name="student_number" className="form-control" value={data.student_number}/> </Col> </FormGroup> <FormGroup row> <Col md={6}> <Label><AiOutlineExport /> Entry Level </Label> <Input readOnly type="text" name="student_entry" className="form-control" value={data.student_entry} /> </Col> <Col md={6}> <Label><AiOutlineExport /> Entry Year </Label> <Input readOnly type="number" name="student_year" className="form-control" value={data.student_year}/> </Col> </FormGroup> <Button color="danger"><AiOutlineDelete /> Delete Data</Button> </Form> </div> ); } export default DeleteStudent; 
Enter fullscreen mode Exit fullscreen mode
  • edit-student.component.jsx
import React, { useState, useEffect } from 'react'; import { Form, FormGroup, Label, Input, Col, Button } from 'reactstrap'; import { AiOutlineUserAdd, AiOutlineUser, AiOutlineExport, AiOutlineForward } from 'react-icons/ai'; import axios from 'axios'; const EditStudent = (props) => { const [data, setData] = useState({ student_name: "", student_address: "", student_number: "", student_entry: "", student_year: "", student_verification: false }); useEffect(() => { const fetchData = async () => { const result = await axios( `http://localhost:4000/all_student/${props.match.params.id}` ); setData({ ...result.data }); }; fetchData(); }, []); const onChangeStudentData = (e) => { setData({ ...data, [e.target.name]: e.target.value }) console.log(data); } const onSubmitStudentData = (e) => { e.preventDefault(); axios.post(`http://localhost:4000/all_student/update/${props.match.params.id}`, data).then(res => console.log(res.data)); props.history.push('/'); } return ( <div style={{ marginTop: 10 }}> <h3><AiOutlineUserAdd /> Edit Student</h3> <Form onSubmit={onSubmitStudentData}> <FormGroup row> <Col> <Label><AiOutlineUser /> Student Name </Label> <Input type="text" name="student_name" className="form-control" value={data.student_name} onChange={onChangeStudentData} /> </Col> </FormGroup> <FormGroup row> <Col> <Label><AiOutlineExport /> Address </Label> <Input type="text" name="student_address" className="form-control" value={data.student_address} onChange={onChangeStudentData} /> </Col> </FormGroup> <FormGroup row> <Col> <Label><AiOutlineExport /> Student Number </Label> <Input type="number" name="student_number" className="form-control" value={data.student_number} onChange={onChangeStudentData} /> </Col> </FormGroup> <FormGroup row> <Col md={6}> <Label><AiOutlineExport /> Entry Level </Label> <Input type="text" name="student_entry" className="form-control" value={data.student_entry} onChange={onChangeStudentData} /> </Col> <Col md={6}> <Label><AiOutlineExport /> Entry Year </Label> <Input type="number" name="student_year" className="form-control" value={data.student_year} onChange={onChangeStudentData} /> </Col> </FormGroup> <FormGroup check row> <Col> <Label check> <Input type="checkbox" name="student_verification" defaultChecked={data.student_verification ? true : false} value={data.student_verification ? Boolean(true) : Boolean(false)} onChange={onChangeStudentData} required />{data.student_verification ? "Data is verified" : "Data isn't verified"} </Label> </Col> </FormGroup> <Button color="primary"><AiOutlineForward /> Verified Data</Button> </Form> </div> ); } export default EditStudent; 
Enter fullscreen mode Exit fullscreen mode
  • list-student.component.jsx
import React, { useState, useEffect } from 'react'; import { Link } from 'react-router-dom'; import axios from 'axios'; import { Table, Badge } from 'reactstrap'; import { AiOutlineEdit, AiOutlineDelete } from 'react-icons/ai'; const ListBar = (props) => { return ( <tr> <td>{props.student.student_name}</td> <td>{props.student.student_number}</td> <td>{props.student.student_entry}</td> <td> {props.student.student_verification ? <Badge color="primary">Verified</Badge> : <Badge color="warning">Not Verified</Badge>} </td> <td> <Link to={"/edit/" + props.student._id}><AiOutlineEdit /></Link> <Link to={"/delete/"+props.student._id}><AiOutlineDelete /></Link> </td> </tr> ); } const ListStudent = () => { const [listData, setListData] = useState({ lists: [] }); useEffect(() => { const fetchData = async () => { const result = await axios( 'http://localhost:4000/all_student/' ); setListData({ lists: result.data }); }; fetchData(); }, []); return ( <div> <h3>List Student</h3> <Table striped style={{ marginTop: 20 }}> <thead> <tr> <th>Student Name</th> <th>Student Number</th> <th>Student Entry</th> <th>Verification</th> <th>Action</th> </tr> </thead> <tbody> {listData.lists.map((current, i) => ( <ListBar student={current} key={i} /> ))} </tbody> </Table> </div> ); } export default ListStudent; 
Enter fullscreen mode Exit fullscreen mode
  • Reorganize your project 🎮

Modify index.html in public folder
Modify the title into School MERN App.

Import all components into App.js.
Add link, router, route from react-router-dom into App.js.

  • src/App.js
import React from 'react'; import "bootstrap/dist/css/bootstrap.min.css"; import './App.css'; import { BrowserRouter as Router, Route, Link } from 'react-router-dom'; import ListStudent from './components/list-student.component'; import EditStudent from './components/edit-student.component'; import CreateStudent from './components/create-student.component'; import DeleteStudent from './components/delete-student.component'; const App = () => { return ( <Router> <div className="container"> <nav className="navbar navbar-expand-lg navbar-dark bg-dark"> <Link to="/" className="navbar-brand">School MERN App</Link> <div className="collapse navbar-collapse"> <ul className="navbar-nav mr-auto"> <li className="navbar-item"> <Link to="/" className="nav-link">List Student</Link> </li> <li className="navbar-item"> <Link to="/create" className="nav-link">Create Student</Link> </li> </ul> </div> </nav> <br /> <Route path="/" exact component={ListStudent} /> <Route path="/edit/:id" component={EditStudent} /> <Route path="/create" component={CreateStudent} /> <Route path="/delete/:id" component={DeleteStudent} /> </div> </Router> ); } export default App; 
Enter fullscreen mode Exit fullscreen mode

Let's take a break ☕ ☕ ☕ before continue to backend!

Let's get started -- Backend ⚡

  • Make a new folder for backend

mkdir backend
cd backend
npm init -y

  • Begin with add backend/server.js as command
const express = require('express'); const app = express(); const bodyParser = require('body-parser'); const cors = require('cors'); const mongoose = require('mongoose'); const PORT = 4000; const crudRoutes = express.Router(); let Crud = require('./crud.model'); app.use(cors()); app.use(bodyParser.json()); mongoose.connect('mongodb://localhost:27017/?readPreference=primary&appname=MongoDB%20Compass%20Community&ssl=false/crud_mern', { useNewUrlParser: true }); const connection = mongoose.connection; connection.once('open', () => { console.log("MongoDB database connection established successfully"); }) crudRoutes.route('/').get((req, res) => { Crud.find((err, results) => { if (err) console.log(err); else res.json(results); }); }); crudRoutes.route('/:id').get((req, res) => { let id = req.params.id; Crud.findById(id, (err, result) => { if (err) console.log(err); else res.json(result); }); }); crudRoutes.route('/add').post((req, res) => { let list = new Crud(req.body); list.save().then(list => { res.status(200).json({'list': 'Student added successfully'}); }).catch(err => { res.status(400).send('Adding failed'); }); }); crudRoutes.route('/update/:id').post((req, res) => { Crud.findById(req.params.id, (err, data) => { if (!data) res.status(404).send("Student is not found"); else { data.student_name = req.body.student_name; data.student_address = req.body.student_address; data.student_number = req.body.student_number; data.student_entry = req.body.student_entry; data.student_year = req.body.student_year; data.student_verification = req.body.student_verification; data.save().then(data => { res.json('Data student is updated!'); }).catch(err => { res.status(400).send("Update isn't possible"); }); } }); }); crudRoutes.route('/delete/:id').delete((req, res) => { Crud.findByIdAndRemove(req.params.id, (err, data) => { if (err) return res.status(500).send("There was a problem deleting the user."); res.status(200).send(`Student ${data.student_name} was deleted`); }) }) app.use('/all_student', crudRoutes); app.listen(PORT, () => { console.log("Server is running on PORT: " + PORT); }) 
Enter fullscreen mode Exit fullscreen mode
  • Add backend/crud.model.js as a Schema for MongoDB
const mongoose = require('mongoose'); const Schema = mongoose.Schema; let crudStudent = new Schema({ student_name: { type: String }, student_address: { type: String }, student_number: { type: Number }, student_entry: { type: String }, student_year: { type: Number }, student_verification: { type: Boolean } }); module.exports = mongoose.model('school_student', crudStudent); 
Enter fullscreen mode Exit fullscreen mode
  • Run backend folder with nodemon server .

Additional:

In backend folder, you will find node_modules, crud.model.js, package.json, package-lock.json, and server.js

Don't forget to install nodemon to run your backend

Final Part - Run the Application

You are finally implementing MERN with your simple create-read-update-delete (CRUD) student app. You can implement it for another project.

Additional Notes

Here are some tools that I use for this:

  • Visual Studio Code
  • Postman
  • MongoDB Compass Community
  • Mozilla Firefox

If there are question about anything, you can contact me always 👓 ❤️ ❤️

Top comments (3)

Collapse
 
hikmatcnn profile image
hkmt hikayat

thank you

Collapse
 
tomgrigory profile image
Tom George

great tut|| Learned many new thingys..

Collapse
 
yongchanghe profile image
Yongchang He

Thank you for the effort of sharing knowledge!