Event Dashboard using MERN Stack
In this article, we’ll walk through the step-by-step process of creating an Event Management System using the MERN (MongoDB, ExpressJS, React, NodeJS) stack. This project will showcase how to set up a full-stack web application where users can manage events by posting details about the event and deleting it when it has been completed.
Preview of final output: Let us have a look at how the final application will look like:
Prerequisites:
Approach to create an Event Dashboard:
- The component displays a event with Titile , Location , Date for each event.
- Dashboard display list of event and allows admin to add new event and update existing event by clicking on corresponding buttons.
- Call Api using axios via useEffect.
- Uses useEffect to handle fetch events and attempts to re-fetch in case of errors.
- In server/server.js file, initialize the server and connect to MongoDB.
- Defines an initial configuration with express.json() and cors.
- server/models/eventSchema.js is the schema for the event model. It contains the title, description, date, and location of the event.
Steps to Create the Backend Server:
Step 1: Create a directory for project
mkdir server
cd server
Step 2: Initialized the Express app and installing the required packages
npm init -y
npm i express mongoose cors nodemon
Project Structure(Backend):
The updated dependencies in package.json file of backend will look like:
"dependencies": {
"cors": "^2.8.5",
"express": "^4.18.3",
"mongoose": "^8.2.1"
}
Example: Write the following code to create backend
// In server/server.js, initialize the server and connect to MongoDB.
import express, { json } from 'express';
import cors from 'cors';
import { connect } from 'mongoose';
import eventRoute from './routes/eventRoute.js';
const app = express();
const port = 4000;
app.use(cors());
app.use(json());
// Connect to MongoDB
connect('mongodb://localhost/mydatabase', {
useNewUrlParser: true,
useUnifiedTopology: true,
})
.then(() => console.log('Connected to MongoDB'))
.catch((error) =>
console.error('Failed to connect to MongoDB', error));
// Define your routes here
app.use('/events', eventRoute);
app.listen(port, () => {
console.log(`Server is running on port ${port}`);
});
// server/routes/eventRoute.js
import { Router } from 'express';
import eventCon from '../controllers/eventCon.js';
const router = Router();
// Define your routes here
router.get('/', eventCon.getAllEvents);
router.get('/:id', eventCon.getEventById);
router.post('/', eventCon.createEvent);
router.put('/:id', eventCon.updateEvent);
router.delete('/:id', eventCon.deleteEvent);
export default router;
// server/models/eventSchema.js is the schema for the event model. It contains the title, description, date, and location of the event.
import { Schema, model } from 'mongoose';
const eventSchema = new Schema({
title: {
type: String,
required: true
},
description: {
type: String,
required: true
},
date: {
type: Date,
required: true
},
location: {
type: String,
required: true
},
});
const Event = model('Event', eventSchema);
export default Event;
// server/controllers/eventCon.js
import Event from '../models/eventSchema.js';
const eventController = {
// Create a new event
createEvent: async (req, res) => {
try {
const { title, description, date, location } = req.body;
const event = new Event({
title,
description,
date,
location
});
await event.save();
res.status(201).json(event);
} catch (error) {
res.status(500).json({
error: error.message
});
}
},
// Get all events
getAllEvents: async (req, res) => {
try {
const events = await Event.find();
res.json(events);
} catch (error) {
res.status(500).json({
error: error.message
});
}
},
// Get a single event by ID
getEventById: async (req, res) => {
try {
const { id } = req.params;
const event = await Event.findById(id);
if (!event) {
return res.status(404).json({
message: 'Event not found'
});
}
res.json(event);
} catch (error) {
res.status(500).json({
error: error.message
});
}
},
// Update an event by ID
updateEvent: async (req, res) => {
try {
const { id } = req.params;
const { title, description, date, location } = req.body;
const event = await Event.findByIdAndUpdate(id,
{
title,
description,
date,
location
},
{ new: true });
if (!event) {
return res.status(404).json({
message: 'Event not found'
});
}
res.json(event);
} catch (error) {
res.status(500).json({
error: error.message
});
}
},
// Delete an event by ID
deleteEvent: async (req, res) => {
try {
const { id } = req.params;
const event = await Event.findByIdAndDelete(id);
if (!event) {
return res.status(404).json({
message: 'Event not found'
});
}
res.json({
message: 'Event deleted successfully'
});
} catch (error) {
res.status(500).json({
error: error.message
});
}
},
};
export default eventController;
Start your application using the following command.
npm run dev
Steps to Create the Frontend Application:
Step 1: Initialize the React App with Vite and installing the required packages.
npm create vite@latest -y
Step 2: Navigate to the root of the project using the following command.
cd client
Step 3: Install the necessary package in your project using the following command.
npm install axios
Step 4: Install the node_modules using the following command.
npm install
Project Structure(Frontend):
The updated dependencies in package.json file of frontend will look like:
{
"dependencies": {
"axios": "^1.6.7",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.22.2"
},
"devDependencies": {
"@types/react": "^18.2.56",
"@types/react-dom": "^18.2.19",
"@vitejs/plugin-react": "^4.2.1",
"eslint": "^8.56.0",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.5",
"vite": "^5.1.4"
}
}
Example: Write the following code in frontend files of the project
/* client/src/index.css */
/**
*
* This file is the CSS stylesheet for the index page of the Event website.
* It contains styles and layout rules specific to the index page.
*/
body{
background: #F4F7FD;
margin-top:20px;
}
.card-margin {
margin-bottom: 1.875rem;
}
.card {
border: 0;
box-shadow: 0px 0px 10px 0px rgba(82, 63, 105, 0.1);
-webkit-box-shadow: 0px 0px 10px 0px rgba(82, 63, 105, 0.1);
-moz-box-shadow: 0px 0px 10px 0px rgba(82, 63, 105, 0.1);
-ms-box-shadow: 0px 0px 10px 0px rgba(82, 63, 105, 0.1);
}
.card {
position: relative;
display: flex;
flex-direction: column;
min-width: 0;
word-wrap: break-word;
background-color: #ffffff;
background-clip: border-box;
border: 1px solid #e6e4e9;
border-radius: 8px;
}
.card .card-header.no-border {
border: 0;
}
.card .card-header {
background: none;
padding: 0 0.9375rem;
font-weight: 500;
display: flex;
align-items: center;
min-height: 50px;
}
.card-header:first-child {
border-radius: calc(8px - 1px) calc(8px - 1px) 0 0;
}
.widget-49 .widget-49-title-wrapper {
display: flex;
align-items: center;
}
.widget-49 .widget-49-title-wrapper .widget-49-date-primary {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
background-color: #edf1fc;
width: 4rem;
height: 4rem;
border-radius: 50%;
}
.widget-49 .widget-49-title-wrapper .widget-49-date-primary .widget-49-date-day {
color: #4e73e5;
font-weight: 500;
font-size: 1.5rem;
line-height: 1;
}
.widget-49 .widget-49-title-wrapper .widget-49-date-primary .widget-49-date-month {
color: #4e73e5;
line-height: 1;
font-size: 1rem;
text-transform: uppercase;
}
.widget-49 .widget-49-title-wrapper .widget-49-date-secondary {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
background-color: #fcfcfd;
width: 4rem;
height: 4rem;
border-radius: 50%;
}
.widget-49 .widget-49-title-wrapper .widget-49-date-secondary .widget-49-date-day {
color: #dde1e9;
font-weight: 500;
font-size: 1.5rem;
line-height: 1;
}
.widget-49 .widget-49-title-wrapper .widget-49-date-secondary .widget-49-date-month {
color: #dde1e9;
line-height: 1;
font-size: 1rem;
text-transform: uppercase;
}
.widget-49 .widget-49-title-wrapper .widget-49-date-success {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
background-color: #e8faf8;
width: 4rem;
height: 4rem;
border-radius: 50%;
}
.widget-49 .widget-49-title-wrapper .widget-49-date-success .widget-49-date-day {
color: #17d1bd;
font-weight: 500;
font-size: 1.5rem;
line-height: 1;
}
.widget-49 .widget-49-title-wrapper .widget-49-date-success .widget-49-date-month {
color: #17d1bd;
line-height: 1;
font-size: 1rem;
text-transform: uppercase;
}
.widget-49 .widget-49-title-wrapper .widget-49-date-info {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
background-color: #ebf7ff;
width: 4rem;
height: 4rem;
border-radius: 50%;
}
.widget-49 .widget-49-title-wrapper .widget-49-date-info .widget-49-date-day {
color: #36afff;
font-weight: 500;
font-size: 1.5rem;
line-height: 1;
}
.widget-49 .widget-49-title-wrapper .widget-49-date-info .widget-49-date-month {
color: #36afff;
line-height: 1;
font-size: 1rem;
text-transform: uppercase;
}
.widget-49 .widget-49-title-wrapper .widget-49-date-warning {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
background-color: floralwhite;
width: 4rem;
height: 4rem;
border-radius: 50%;
}
.widget-49 .widget-49-title-wrapper .widget-49-date-warning .widget-49-date-day {
color: #FFC868;
font-weight: 500;
font-size: 1.5rem;
line-height: 1;
}
.widget-49 .widget-49-title-wrapper .widget-49-date-warning .widget-49-date-month {
color: #FFC868;
line-height: 1;
font-size: 1rem;
text-transform: uppercase;
}
.widget-49 .widget-49-title-wrapper .widget-49-date-danger {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
background-color: #feeeef;
width: 4rem;
height: 4rem;
border-radius: 50%;
}
.widget-49 .widget-49-title-wrapper .widget-49-date-danger .widget-49-date-day {
color: #F95062;
font-weight: 500;
font-size: 1.5rem;
line-height: 1;
}
.widget-49 .widget-49-title-wrapper .widget-49-date-danger .widget-49-date-month {
color: #F95062;
line-height: 1;
font-size: 1rem;
text-transform: uppercase;
}
.widget-49 .widget-49-title-wrapper .widget-49-date-light {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
background-color: #fefeff;
width: 4rem;
height: 4rem;
border-radius: 50%;
}
.widget-49 .widget-49-title-wrapper .widget-49-date-light .widget-49-date-day {
color: #f7f9fa;
font-weight: 500;
font-size: 1.5rem;
line-height: 1;
}
.widget-49 .widget-49-title-wrapper .widget-49-date-light .widget-49-date-month {
color: #f7f9fa;
line-height: 1;
font-size: 1rem;
text-transform: uppercase;
}
.widget-49 .widget-49-title-wrapper .widget-49-date-dark {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
background-color: #ebedee;
width: 4rem;
height: 4rem;
border-radius: 50%;
}
.widget-49 .widget-49-title-wrapper .widget-49-date-dark .widget-49-date-day {
color: #394856;
font-weight: 500;
font-size: 1.5rem;
line-height: 1;
}
.widget-49 .widget-49-title-wrapper .widget-49-date-dark .widget-49-date-month {
color: #394856;
line-height: 1;
font-size: 1rem;
text-transform: uppercase;
}
.widget-49 .widget-49-title-wrapper .widget-49-date-base {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
background-color: #f0fafb;
width: 4rem;
height: 4rem;
border-radius: 50%;
}
.widget-49 .widget-49-title-wrapper .widget-49-date-base .widget-49-date-day {
color: #68CBD7;
font-weight: 500;
font-size: 1.5rem;
line-height: 1;
}
.widget-49 .widget-49-title-wrapper .widget-49-date-base .widget-49-date-month {
color: #68CBD7;
line-height: 1;
font-size: 1rem;
text-transform: uppercase;
}
.widget-49 .widget-49-title-wrapper .widget-49-meeting-info {
display: flex;
flex-direction: column;
margin-left: 1rem;
}
.widget-49 .widget-49-title-wrapper .widget-49-meeting-info .widget-49-pro-title {
color: #3c4142;
font-size: 14px;
}
.widget-49 .widget-49-title-wrapper .widget-49-meeting-info .widget-49-meeting-time {
color: #B1BAC5;
font-size: 13px;
}
.widget-49 .widget-49-meeting-points {
font-weight: 400;
font-size: 13px;
margin-top: .5rem;
}
.widget-49 .widget-49-meeting-points .widget-49-meeting-item {
display: list-item;
color: #727686;
}
.widget-49 .widget-49-meeting-points .widget-49-meeting-item span {
margin-left: .5rem;
}
.widget-49 .widget-49-meeting-action {
text-align: right;
}
.widget-49 .widget-49-meeting-action a {
text-transform: uppercase;
}
// client/src/main.jsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import { BrowserRouter, Route, Routes } from 'react-router-dom'
import App from './App.jsx'
import Home from './Home.jsx'
import Update from './Update.jsx'
import "./App.css"
ReactDOM.createRoot(document.getElementById('root')).render(
<BrowserRouter>
<React.StrictMode>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/dashboard" element={<App />} />
<Route path="/update/:id" element={<Update />} />
</Routes>
</React.StrictMode>
</BrowserRouter>
)
// client/src/Home.jsx
import React, {
useEffect,
useState
} from 'react';
import { Link } from 'react-router-dom';
import axios from 'axios';
const Home = () => {
const [events, setEvents] = useState([]);
useEffect(() => {
axios.get('http://localhost:4000/events')
.then(response => {
setEvents(response.data);
console.log(response.data);
})
.catch(error => {
console.error('Error fetching events:',
error);
});
}, []);
return (
<div>
<div className="container">
<nav className="navbar navbar-expand-lg
navbar-light bg-light">
<div className="container my-2">
<h4>GFG Event</h4>
<Link className="btn btn-primary ml-auto"
to="/dashboard">
Dashboard
</Link>
</div>
</nav>
<div className="row my-3">
{events.map(event => {
const date = new Date(event.date);
const day = date.getDate();
const month = date.toLocaleString('default',
{ month: 'short' });
const year = date.getFullYear();
return (
<div className="col-lg-4" key={event.id}>
<div className="card card-margin">
<div className="card-body pt-2">
<div className="widget-49">
<div className="widget-49-title-wrapper">
<div className="widget-49-date-primary">
<span className="widget-49-date-day">
{day}
</span>
<span className="widget-49-date-month">
{month}
</span>
</div>
<div className="widget-49-meeting-info">
<span className="widget-49-pro-title">
<b>{event.title}</b>
</span>
<span className="widget-49-pro-title">
<b>{event.location}</b>
</span>
</div>
</div>
<div className="widget-49-meeting-points">
<span>{event.description}</span>
</div>
<div className="widget-49-meeting-action">
<a href="#"
className="btn btn-sm btn-flash-border-primary">
{`${day}-${month}-${year}`}
</a>
</div>
</div>
</div>
</div>
</div>
)
})}
</div>
</div>
</div>
);
};
export default Home;
// client/src/App.jsx
import React, { useEffect, useState } from 'react';
import axios from 'axios';
import './App.css'
import { Link } from 'react-router-dom';
function App() {
const [events, setEvents] = useState([]);
const [isSuccess, setIsSuccess] = useState(false);
useEffect(() => {
axios.get('http://localhost:4000/events')
.then(response => {
setEvents(response.data);
})
.catch(error => {
console.error('Error fetching events:', error);
});
}, [isSuccess]);
const [formData, setFormData] = useState({
title: '',
description: '',
location: '',
date: ''
});
const handleInputChange = (e) => {
setFormData({
...formData,
[e.target.name]: e.target.value
});
};
const handleSubmit = (e) => {
e.preventDefault();
axios.post('http://localhost:4000/events',
formData)
.then(response => {
// handle success
console.log('Event added successfully:', response.data);
// Reset the form
setFormData({
title: '',
description: '',
location: '',
date: ''
});
setIsSuccess(true);
})
.catch(error => {
// handle error
setIsSuccess(false);
console.error('Error adding event:', error);
});
};
const handleDelete = (id) => {
axios.delete(`http://localhost:4000/events/${id}`)
.then(response => {
// handle success
console.log('Event deleted successfully:', response.data);
setIsSuccess(true);
})
.catch(error => {
// handle error
setIsSuccess(false);
console.error('Error deleting event:', error);
});
};
return (
<div>
<div class="">
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container my-2">
<h4>GFG Event Dashboard</h4>
<div>
<button type="button"
class="btn btn-success mx-3" data-toggle="modal"
data-target="#exampleModal">
Add Event
</button>
<Link class="btn btn-primary ml-auto" to="/">
Home
</Link>
</div>
</div>
</nav>
<div class="container">
<h5 class="text-center my-2">List of Events</h5>
<table class="table table-striped border">
<thead>
<tr>
<th scope="col">Id</th>
<th scope="col">Title</th>
<th scope="col">Date</th>
<th scope="col">Update</th>
<th scope="col">Delete</th>
</tr>
</thead>
<tbody>
{events?.map(event => (
<tr key={event._id}>
<th>{event._id}</th>
<th>{event.title}</th>
<td>{event.date}</td>
<td><Link class="btn btn-primary ml-auto"
to={`/update/${event._id}`}>
Update
</Link></td>
<td><button onClick={() => handleDelete(event._id)}
className="btn btn-danger">Delete</button></td>
</tr>
))}
</tbody>
</table>
</div>
</div>
<div class="modal fade" id="exampleModal"
tabindex="-1" role="dialog"
aria-labelledby="exampleModalLabel"
aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">
Add New Event
</h5>
<button type="button" class="close"
data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<form onSubmit={handleSubmit}>
<div class="form-group">
<label for="inputAddress">Title</label>
<input onChange={handleInputChange}
value={formData.title} type="text"
class="form-control" name="title"
id="inputAddress" placeholder="Event Title" />
</div>
<div class="form-group mt-2">
<label for="inputAddress2">Description</label>
<input onChange={handleInputChange}
value={formData.description} type="text"
class="form-control" name="description"
id="inputAddress2" placeholder="Enter Description" />
</div>
<div class="form-group mt-2">
<label for="inputAddress2">Location</label>
<input onChange={handleInputChange}
value={formData.location} type="text"
class="form-control" name="location"
id="inputAddress2" placeholder="Enter Location" />
</div>
<div class="form-group mt-2">
<label for="inputAddress2">Date</label>
<input onChange={handleInputChange}
value={formData.date} type="date"
class="form-control" name="date"
id="inputAddress2" placeholder="Enter Date" />
</div>
<button type="submit"
class="btn btn-primary mt-3">
Add Event
</button>
</form>
</div>
</div>
</div>
</div>
</div>
);
}
export default App;
// client/src/Update.jsx
import axios from 'axios';
import React, { useState, useEffect } from 'react';
import { Link, useParams } from 'react-router-dom';
function Update() {
const { id } = useParams();
const [event, setEvent] = useState([]);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
axios
.get(`http://localhost:4000/events/${id}`)
.then(response => {
setEvent(response.data);
setIsLoading(false);
console.log(response.data);
})
.catch(error => {
// Handle error
alert('Error fetching event:', error);
});
}, [id]);
const [updatedTitle, setUpdatedTitle] = useState(event ? event.title : '');
const [updatedDescription, setUpdatedDescription] = useState(event ? event.description : '');
const [updatedLocation, setUpdatedLocation] = useState(event ? event.location : '');
const [updatedDate, setUpdatedDate] = useState(event ? event.dat : '');
const handleUpdate = e => {
e.preventDefault();
const updatedEvent = {
title: updatedTitle,
description: updatedDescription,
location: updatedLocation,
date: updatedDate
};
axios
.put(`http://localhost:4000/events/${id}`, updatedEvent)
.then(() => {
// Handle successful update
alert('Event updated successfully!');
})
.catch(error => {
// Handle error
alert('Error updating event:', error);
});
};
return (
<div className="container">
<nav className="navbar navbar-expand-lg navbar-light bg-light">
<div className="container my-2">
<h4>GFG Event</h4>
<Link className="btn btn-primary ml-auto" to="/dashboard">
Dashboard
</Link>
</div>
</nav>
<div className="row my-3">
<div className="col-lg-4">
{isLoading ? (
<h3>Loading...</h3>
) : (
<form onSubmit={handleUpdate}>
<div className="form-group">
<label htmlFor="inputAddress">Title</label>
<input
type="text"
className="form-control"
name="title"
id="inputAddress"
placeholder="Event Title"
defaultValue={event.title}
onChange={e => setUpdatedTitle(e.target.value)}
/>
</div>
<div className="form-group mt-2">
<label htmlFor="inputAddress2">Description</label>
<input
type="text"
className="form-control"
name="description"
id="inputAddress2"
placeholder="Enter Description"
defaultValue={event.description}
onChange={e => setUpdatedDescription(e.target.value)}
/>
</div>
<div className="form-group mt-2">
<label htmlFor="inputAddress2">Location</label>
<input
type="text"
className="form-control"
name="location"
id="inputAddress2"
placeholder="Enter Location"
defaultValue={event.location}
onChange={e => setUpdatedLocation(e.target.value)}
/>
</div>
<div className="form-group mt-2">
<label htmlFor="inputAddress2">Date</label>
<input
type="date"
className="form-control"
name="date"
id="inputAddress2"
placeholder="Enter Date"
defaultValue={event.date}
onChange={e => setUpdatedDate(e.target.value)}
/>
</div>
<button type="submit" className="btn btn-primary mt-3">
Update
</button>
</form>
)}
</div>
</div>
</div>
);
}
export default Update;
To start client server:
npm run dev
Output:
- Data Saved in Database
- Browser Output