Fundraiser Platform using MEAN
Creating a fundraiser platform using the MEAN stack (MongoDB, Express.js, Angular, and Node.js) provides a powerful combination of technologies for building dynamic and scalable web applications. In this article, we will see a step-wise process of creating the application.
Project Preview:
Approach to build a fundraiser application using MEAN
- Set Up Backend with Express, Node, and Mongoose.
- Install Express.js, Node.js, and Mongoose to create a backend server.
- Initialize an Express application and configure routes to handle HTTP requests.
- Connect to MongoDB Database.
- Set up a connection to MongoDB using Mongoose and define Mongoose schemas to structure and model the data for the application.
- Implement API Endpoints and Define routes to handle requests for creating, reading, updating, and deleting data.
- Use Angular CLI to generate a new Angular project.
- Navigate to the project directory and install required dependencies.
- Set Up Angular Components and Services.
- Create Angular components to define the user interface and structure of the application.
- Develop Angular services to encapsulate business logic and interact with backend APIs.
Steps to Create Server
Step 1: Create a new directory named backend.
mkdir backend
cd backend
Step 2: Create a server using the following command in your terminal.
npm init -y
Step 3: Install the necessary package in your server using the following command.
npm install express mongoose cors
Dependencies:
"dependencies": {
"cors": "^2.8.5",
"express": "^4.19.2",
"mongoose": "^8.3.2"
}
Folder Structure:
Step 5: Create a file ‘index.js’ and set up the server.
//index.js
const express = require("express");
const mongoose = require("mongoose");
const cors = require("cors");
const app = express();
const PORT = process.env.PORT || 5000;
app.use(cors());
// Connect to MongoDB
mongoose.connect("<MONGODB_URL>", {
useNewUrlParser: true,
useUnifiedTopology: true,
});
// Define a Donation schema
const donationSchema = new mongoose.Schema({
name: { type: String, required: true },
amount: { type: Number, required: true },
description: { type: String, required: true },
});
const Donation = mongoose.model("Donation", donationSchema);
// Define a Total Amount schema
const totalAmountSchema = new mongoose.Schema({
total: { type: Number, required: true },
current: { type: Number, required: true },
});
const TotalAmount = mongoose.model("TotalAmount", totalAmountSchema);
// GET route to fetch total amount
app.get("/api/totalAmount", async (req, res) => {
try {
// Fetch total amount
const totalAmount = await TotalAmount.findOne();
res.json(totalAmount);
} catch (error) {
console.error(error);
res.status(500).send("Internal Server Error");
}
});
// PUT route to update current amount
app.put("/api/totalAmount", async (req, res) => {
try {
const { current } = req.body;
// Update current amount
const totalAmount = await TotalAmount.findOne();
totalAmount.current = current;
await totalAmount.save();
res.status(200).send("Current amount updated successfully");
} catch (error) {
console.error(error);
res.status(500).send("Internal Server Error");
}
});
// API routes for donations
app.post("/api/donations", async (req, res) => {
try {
const { name, amount, description } = req.body;
// Create a new donation
const newDonation = new Donation({ name, amount, description });
await newDonation.save();
// Update the current total amount
const totalAmount = await TotalAmount.findOne();
totalAmount.current += amount;
await totalAmount.save();
res.status(201).json(newDonation);
} catch (error) {
console.error(error);
res.status(500).send("Internal Server Error");
}
});
app.get("/api/donations", async (req, res) => {
try {
// Fetch all donations
const donations = await Donation.find();
res.json(donations);
} catch (error) {
console.error(error);
res.status(500).send("Internal Server Error");
}
});
// Start the server
app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
});
Start your server using the following command.
node index.js
Steps to Create the Frontend
Step 1: Setup Angular Project
Make sure you have Angular CLI installed. If not, you can install it globally using npm:
npm install -g @angular/cli
Step 2: Set up the repository
ng new fundraiser-app
cd fundraiser-app
Step 3: Install Angular Material
ng add @angular/material
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",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.14.3"
},
Project Structure:
Example: Create the files according to the project structure and write the following code in respective files.
<!-- app.component.html -->
<div class="App">
<header>
<div class="navbar">
<img
src="https://media.w3wiki.net/gfg-gg-logo.svg"
alt="w3wiki Logo"
/>
<h1>Fund raiser</h1>
</div>
</header>
<!-- Donation Form -->
<div class="app-container">
<div class="donation-form">
<label>
Name:
<input type="text" [(ngModel)]="name" />
</label>
<br />
<label>
Amount:
<input type="number" [(ngModel)]="amount" />
</label>
<br />
<label>
Description:
<input type="text" [(ngModel)]="description" />
</label>
<br />
<button (click)="handleDonate()">Donate</button>
</div>
</div>
<!-- Donation Statistics -->
<div>
<h2>Donation Statistics:</h2>
<div class="div_stats" id="current">
<p>Current Amount: {{ currentTotal }}</p>
</div>
<div class="div_stats" id="total">
<p>Amount Required: {{ totalAmount?.total }}</p>
</div>
</div>
<!-- Donations Table -->
<div>
<h2>Donations:</h2>
<table>
<thead>
<tr>
<th>Name</th>
<th>Amount</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let donation of donations">
<td>{{ donation.name }}</td>
<td>{{ donation.amount }}</td>
<td>{{ donation.description }}</td>
</tr>
</tbody>
</table>
</div>
</div>
/* app.component.css */
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
background-color: #f4f4f4;
}
header {
background-color: #333;
color: #fff;
text-align: center;
padding: 1em 0;
}
section {
max-width: 600px;
margin: 2em auto;
background-color: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
#totalAmount {
font-size: 24px;
margin-bottom: 20px;
}
form {
display: flex;
flex-direction: column;
}
label {
margin-bottom: 8px;
}
input {
padding: 10px;
margin-bottom: 16px;
border: 1px solid #ccc;
border-radius: 4px;
}
button {
background-color: #4caf50;
color: #fff;
padding: 10px;
border: none;
border-radius: 4px;
cursor: pointer;
}
/* General styling */
table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
}
/* Table header */
th {
background-color: #4caf50;
color: white;
padding: 12px 15px;
text-align: left;
}
/* Table rows */
tr {
border-bottom: 1px solid #ddd;
}
tr:nth-child(even) {
background-color: #f2f2f2;
}
td {
padding: 12px 15px;
text-align: left;
}
tr:hover {
background-color: #e0f7fa;
transition: background-color 0.3s ease;
}
.donation-form label {
display: block;
margin-bottom: 10px;
}
.donation-form input {
width: 100%;
padding: 10px;
margin-bottom: 16px;
border: 1px solid #ccc;
border-radius: 4px;
}
.div_stats {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
margin-bottom: 10px;
border: 1px solid #ccc;
border-radius: 5px;
background-color: #f9f9f9;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.div_stats p {
margin: 0;
font-weight: bold;
font-size: 16px;
}
#current {
border-color: #4caf50;
}
#total {
border-color: #f44336;
}
header {
background-color: #333;
color: #fff;
text-align: center;
}
.navbar {
display: flex;
align-items: center;
background-color: #333;
color: #fff;
height: 30px;
margin-bottom: 10px;
}
.navbar img {
width: 70px;
height: 80px;
margin-right: 10px;
}
.app-container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
// app.component.spec.ts
import { TestBed } from "@angular/core/testing";
import { AppComponent } from "./app.component";
describe("AppComponent", () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [AppComponent],
}).compileComponents();
});
it("should create the app", () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app).toBeTruthy();
});
it(`should have the 'fundraiser-app' title`, () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app.title).toEqual("fundraiser-app");
});
it("should render title", () => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.nativeElement as HTMLElement;
expect(compiled.querySelector("h1")?.textContent).toContain(
"Hello, fundraiser-app"
);
});
});
// app.component.ts
import { Component, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';
interface Donation {
name: string;
amount: number;
description: string;
}
interface TotalAmount {
total: number;
current: number;
}
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
donations: Donation[] = [];
amount: number = 0;
description: string = '';
name: string = '';
currentTotal: number = 0;
totalAmount: TotalAmount | undefined;
constructor(private http: HttpClient) { }
ngOnInit(): void {
this.fetchDonations();
this.fetchTotalAmount();
}
fetchDonations(): void {
this.http.get<Donation[]>('http://localhost:5000/api/donations')
.subscribe(donations => {
this.donations = donations;
});
}
fetchTotalAmount(): void {
this.http.get<TotalAmount>('http://localhost:5000/api/totalAmount')
.subscribe(
totalAmount => {
this.totalAmount = totalAmount;
this.currentTotal = totalAmount.current;
},
error => {
console.error('Error fetching total amount:', error);
}
);
}
handleDonate(): void {
const newAmount = this.amount;
this.http.post<Donation>('http://localhost:5000/api/donations', {
amount: newAmount,
description: this.description,
name: this.name
})
.subscribe(
response => {
this.donations.push(response);
this.amount = 0;
this.description = '';
this.name = '';
this.currentTotal += newAmount;
},
error => {
console.error('Error donating:', error);
}
);
}
}
//app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [provideRouter(routes)]
};
//app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent,
],
imports: [
BrowserModule,
HttpClientModule,
FormsModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
//main.ts
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
// You can uncomment this line if you want to enable production mode
// enableProdMode();
platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.error(err));
Step 4: Start the app using the following command.
ng serve
Output:
Contact Us