Event Management Web App using MEAN
In this guide, we’ll walk through the step-by-step process of building a feature-rich Event Management Web App. We will make use of the MEAN stack, i.e. MongoDB, ExpressJS, Angular and NodeJS, to build this project.
Project Preview:
Prerequisites:
Approach to create Event Management Web App
- Define the structure of an event using Mongoose schemas in a model file (e.g., `Event.js`).
- Develop routes for handling Create, Read, Update, and Delete (CRUD) operations in a dedicated `eventRoutes.js` file.
- Set up a MongoDB database and establish a connection in your Express application.
- Create a server file (e.g., `server.js`) where Express is configured to listen on a specific port.
- Design and implement a form inside our app component (`app.component.ts`) for adding new events.
- Display all events using ngFor directive inside a card to display a list of events fetched from the server.
- Style your components for an engaging user interface. We will be utilizing Tailwind CSS .
Steps to Create Application:
Step 1: Creating express app using the following command:
npm init -y
Step 2: Installing the required packages
npm install express mongoose body-parser cors
Folder Structure(backend):
The updated dependencies in package.json file for backend will look like:
"dependencies": {
"body-parser": "^1.20.2",
"cors": "^2.8.5",
"express": "^4.19.2",
"mongoose": "^8.3.0"
}
Step 3: Below is the code for the backend.
// server.js
const express = require("express");
const mongoose = require("mongoose");
const bodyParser = require("body-parser");
const cors = require("cors");
const eventRoutes = require("./routes/eventRoutes");
const app = express();
const PORT = process.env.PORT || 4000;
app.use(cors());
app.use(bodyParser.json());
MONGO_URI = "YOUR_MONGO_URI";
mongoose.connect(MONGO_URI).then(() => {
console.log("Connected to MongoDB!");
});
app.use("/api", eventRoutes);
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
// routes/eventRoutes.js
const express = require("express");
const router = express.Router();
const Event = require("../models/Event");
router.get("/getAll", async (req, res) => {
try {
const events = await Event.find();
res.json(events);
} catch (error) {
res.status(500).json({ message: error.message });
}
});
router.get("/get/:id", async (req, res) => {
try {
const event = await Event.findById(req.params.id);
res.json(event);
} catch (error) {
res.status(500).json({ message: error.message });
}
});
router.post("/add", async (req, res) => {
const event = new Event({
title: req.body.title,
location: req.body.location,
date: req.body.date,
remind: req.body.remind,
});
try {
const newEvent = await event.save();
console.log(newEvent);
res.status(201).json(newEvent);
} catch (error) {
res.status(400).json({ message: error.message });
}
});
router.delete("/delete/:id", async (req, res) => {
try {
await Event.findByIdAndDelete(req.params.id);
res.json({ message: "Event deleted" });
} catch (error) {
console.error("Error deleting event:", error);
res.status(500).json({ message: error.message });
}
});
router.put("/update/:id", async (req, res) => {
const eventId = req.params.id;
const { title, date, location, remind } = req.body;
try {
const event = await Event.findById(eventId);
if (!event) {
return res.status(404).json({ message: "Event not found" });
}
event.title = title;
event.date = date;
event.location = location;
event.remind = remind;
await event.save();
console.log(event);
res.json(event);
} catch (error) {
console.error("Error updating event:", error);
res.status(500).json({ message: "Internal Server Error" });
}
});
module.exports = router;
// models/Event.js
const mongoose = require("mongoose");
const eventSchema = new mongoose.Schema({
title: { type: String, required: true },
date: { type: Date, required: true },
location: { type: String, required: true },
remind: { type: Boolean, default: false },
});
const Event = mongoose.model("Event", eventSchema);
module.exports = Event;
Step 4: Start the backend server
node server.js
Step 5: Create Angular Project using the following command.
ng new frontend
NOTE: While creating the project, choose ‘Sass (SCSS)’ when asked ‘Which stylesheet format would you like to use?’.
Step 6: Switch to the project directory:
cd frontend
Step 7: Installing the required packages:
npm install tailwindcss @tailwindcss/forms ngx-toastr
Folder Structure(frontend):
Dependencies:
"dependencies": {
"@angular/animations": "^17.3.0",
"@angular/common": "^17.3.0",
"@angular/compiler": "^17.3.0",
"@angular/core": "^17.3.0",
"@angular/forms": "^17.3.0",
"@angular/platform-browser": "^17.3.0",
"@angular/platform-browser-dynamic": "^17.3.0",
"@angular/router": "^17.3.0",
"@tailwindcss/forms": "^0.5.7",
"ngx-toastr": "^18.0.0",
"rxjs": "~7.8.0",
"tailwindcss": "^3.4.3",
"zone.js": "~0.14.3"
}
Step 8: Create the Tailwind Configuration file (`tailwind.config.js`) using below command:
npx tailwindcss init
Step 9: Create a Angular service using below command.
ng generate service api
Step 10: Add the following codes in the required files.
<!-- src/app/app.component.html -->
<div class="flex items-center justify-center">
<div class="w-full max-w-xl">
<h2 class="text-center text-3xl
font-semibold leading-7 text-green-600 mt-2">
Event Management Web App
</h2>
<div class="rounded overflow-hidden shadow-lg mt-5">
<form class="px-6 py-4 bg-gray-100">
<div class="space-y-12">
<div class="border-b border-gray-900/10 pb-4">
<div class="grid grid-cols-1 gap-x-6
gap-y-4 sm:grid-cols-6">
<div class="sm:col-span-6">
<label for="title"
class="block text-sm
font-medium leading-6 text-gray-900
required">Title</label>
<div class="mt-2">
<input type="text" [(ngModel)]="title"
name="title" id="title" autocomplete="title"
class="block w-full rounded-md border-0
py-1.5 text-gray-900 shadow-sm ring-1
ring-inset ring-gray-300 placeholder:text-gray-400
focus:ring-2 focus:ring-inset
focus:ring-indigo-600 sm:text-sm sm:leading-6"
placeholder="Event Title" required />
</div>
</div>
<div class="sm:col-span-3">
<label for="location"
class="block text-sm font-medium leading-6
text-gray-900 required">Location</label>
<div class="mt-2">
<select [(ngModel)]="location" id="location"
name="location" autocomplete="location"
class="block w-full rounded-md border-0
py-1.5 text-gray-900 shadow-sm ring-1
ring-inset ring-gray-300 focus:ring-2
focus:ring-inset focus:ring-indigo-600
sm:max-w-xs sm:text-sm sm:leading-6"
required>
<option>Select</option>
<option>India</option>
<option>United States</option>
<option>Australia</option>
<option>Japan</option>
<option>United Kingdom</option>
<option>France</option>
</select>
</div>
</div>
<div class="sm:col-span-3">
<label for="date"
class="block text-sm font-medium leading-6
text-gray-900 required">Date</label>
<div class="mt-2">
<input [(ngModel)]="date" id="date"
name="date" type="date"
class="block w-full rounded-md
border-0 py-1.5 text-gray-900 shadow-sm
ring-1 ring-inset ring-gray-300 focus:ring-2
focus:ring-inset focus:ring-indigo-600
sm:max-w-xs sm:text-sm sm:leading-6"
required />
</div>
</div>
<div class="sm:col-span-6">
<div class="relative flex gap-x-3">
<div class="flex h-6 items-center">
<input [(ngModel)]="remind" id="remind"
name="remind" type="checkbox"
class="h-4 w-4 rounded border-gray-300
text-indigo-600 focus:ring-indigo-600" />
</div>
<div class="text-sm leading-6">
<label for="remind" class="font-medium
text-gray-900">Remind Me</label>
<p class="text-gray-500">
Get notified about this event an hour before.
</p>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="mt-3 flex items-center justify-end gap-x-6">
<button type="button" class="text-sm font-semibold
leading-6 text-gray-900" (click)="clear()">
Clear
</button>
<button *ngIf="isEditing" type="submit"
class="rounded-md bg-indigo-600 px-3 py-2
text-sm font-semibold text-white shadow-sm
hover:bg-indigo-500 focus-visible:outline
focus-visible:outline-2 focus-visible:outline-offset-2
focus-visible:outline-indigo-600"
(click)="onUpdateClick()">
Update
</button>
<button *ngIf="!isEditing" type="submit"
class="rounded-md bg-indigo-600 px-3 py-2 text-sm
font-semibold text-white shadow-sm
hover:bg-indigo-500 focus-visible:outline
focus-visible:outline-2
focus-visible:outline-offset-2
focus-visible:outline-indigo-600"
(click)="onAddClick()">
Add
</button>
</div>
</form>
</div>
</div>
</div>
<div class="m-5">
<h2 class="text-center text-3xl font-semibold leading-7
text-green-600 mt-2 mb-5">
List Of Upcoming Events
</h2>
<div class="grid grid-cols-1 gap-x-6 gap-y-4 sm:grid-cols-4">
<div *ngFor="let event of eventsArray">
<div class="max-w-xs rounded overflow-hidden
shadow-lg bg-gray-100">
<div class="px-6 py-4">
<div class="font-bold text-xl mb-2">{{ event.title }}</div>
<div class="flex items-start justify-between">
<div class="text-gray-700 text-sm">
{{ event.date | date : "fullDate" }}
<p></p>
</div>
<div *ngIf="event.remind">
<svg viewBox="0 0 24 24" width="20"
height="20" xmlns="http://www.w3.org/2000/svg">
<path
d="m5.705 3.71-1.41-1.42C1
5.563 1 7.935 1 11h1l1-.063C3 8.009 3
6.396 5.705 3.71zm13.999-1.42-1.408 1.42C21
6.396 21 8.009 21 11l2-.063c0-3.002
0-5.374-3.296-8.647zM12 22a2.98 2.98 0 0 0
2.818-2H9.182A2.98 2.98 0 0 0 12
22zm7-7.414V10c0-3.217-2.185-5.927-5.145-6.742C13.562
2.52 12.846 2 12 2s-1.562.52-1.855 1.258C7.184
4.073 5 6.783 5 10v4.586l-1.707 1.707A.996.996
0 0 0 3 17v1a1 1 0 0 0 1 1h16a1
1 0 0 0 1-1v-1a.996.996 0 0 0-.293-.707L19 14.586z" />
</svg>
</div>
</div>
</div>
<div class="px-6 mb-2">
<span
class="inline-block bg-gray-200 rounded-full
px-3 py-1 text-xs font-semibold
text-gray-700 mr-2 mb-2">{{
event.location }}</span>
</div>
<div class="flex items-center
justify-center gap-x-3 my-2">
<button type="button"
class="rounded-md bg-yellow-500
px-5 py-2 text-sm font-semibold
text-white shadow-sm hover:bg-yellow-400
focus-visible:outline focus-visible:outline-2
focus-visible:outline-offset-2 focus-visible:outline-yellow-600"
(click)="onEditEvent(event._id)">
Edit
</button>
<button type="button"
class="rounded-md bg-red-600 px-3 py-2 text-sm
font-semibold text-white shadow-sm hover:bg-red-500
focus-visible:outline focus-visible:outline-2
focus-visible:outline-offset-2
focus-visible:outline-red-600"
(click)="onDeleteEvent(event._id)">
Delete
</button>
</div>
</div>
</div>
</div>
</div>
/* styles.scss */
@tailwind base;
@tailwind components;
@tailwind utilities;
@import "ngx-toastr/toastr";
/* src/app/app.component.scss */
.required:after {
content: " *";
color: red;
}
// tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["./src/**/*.{html,js}"],
theme: {
extend: {},
},
plugins: [require('@tailwindcss/forms')],
};
// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
import { provideHttpClient } from '@angular/common/http';
import { provideAnimations } from '@angular/platform-browser/animations';
import { provideToastr } from 'ngx-toastr';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
provideHttpClient(),
provideAnimations(),
provideToastr(),
],
};
// api.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root',
})
export class ApiService {
constructor(private http: HttpClient) { }
getAllEvents(): Observable<any> {
return this.http.get<any>('http://localhost:4000/api/getAll');
}
getEvent(id: number, data: any): Observable<any> {
return this.http.post<any>('http://localhost:4000/api/get/' + id, data);
}
addEvent(data: any): Observable<any> {
return this.http.post<any>('http://localhost:4000/api/add', data);
}
updateEvent(id: string, data: any): Observable<any> {
return this.http.put<any>('http://localhost:4000/api/update/' + id, data);
}
deleteEvent(id: string): Observable<any> {
return this.http.delete<any>('http://localhost:4000/api/delete/' + id);
}
}
// src/app/app.component.ts
import { Component, OnInit } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { FormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
import { ApiService } from './api.service';
import { ToastrService } from 'ngx-toastr';
@Component({
selector: 'app-root',
standalone: true,
imports: [RouterOutlet, FormsModule, CommonModule],
templateUrl: './app.component.html',
styleUrl: './app.component.css',
})
export class AppComponent implements OnInit {
title: string = '';
date: string = '';
location: string = 'Select';
remind: boolean = false;
isEditing: boolean = false;
eventId: string = '';
eventsArray: any[] = [];
constructor(private apiService:
ApiService, private toastr: ToastrService) { }
ngOnInit(): void {
this.apiService.getAllEvents().subscribe((res) => {
this.eventsArray = res;
});
}
onAddClick(): void {
const data = {
title: this.title,
date: this.date,
location: this.location,
remind: this.remind,
};
this.apiService.addEvent(data).subscribe((res) => {
this.eventsArray.push(res);
this.clear();
this.toastr.success('New event added successfully', 'Success!');
});
}
onEditEvent(id: string): void {
const index = this.eventsArray
.findIndex((ev) => ev._id === id);
const event = this.eventsArray[index];
this.title = event['title'];
this.location = event['location'];
this.date = new Date(event['date'])
.toISOString().slice(0, 10);
this.remind = event['remind'];
this.isEditing = true;
this.eventId = id;
}
clear(): void {
this.title = '';
this.date = '';
this.location = 'Select';
this.remind = false;
}
onUpdateClick(): void {
const data = {
title: this.title,
date: this.date,
location: this.location,
remind: this.remind,
};
this.apiService.updateEvent(this.eventId, data).subscribe((res) => {
this.eventsArray = this.eventsArray.filter(
(ev) => ev._id !== this.eventId
);
this.eventsArray.push(res);
this.eventId = '';
this.isEditing = false;
this.toastr.success('Event updated successfully', 'Success!');
this.clear();
});
}
onDeleteEvent(id: string): void {
this.apiService.deleteEvent(id).subscribe((res) => {
this.eventsArray = this.eventsArray.filter((ev) => ev._id !== id);
this.toastr.success('Event deleted successfully!', 'Success!');
});
}
}
Step 11: Start the frontend angular application
ng serve --open
Contact Us