Real Estate Management using MEAN
In this tutorial, we’ll create a Real Estate Management Application using Angular for the front end and Express with MongoDB for the back end. With the help of modern web technologies, we can develop applications to streamline property management tasks.
Project Preview:
Prerequisites
Approach to Create Real Estate Management
- Frontend: Use Angular to create a user-friendly interface for the Real Estate Management Application.
- Backend: Utilize Express with MongoDB to handle data storage and retrieval.
- Integration: Connect the frontend and backend to enable seamless communication between the user interface and the database.
- Functionality: Implement features such as viewing properties, adding new properties, adding reviews, and deleting properties to ensure comprehensive management capabilities.
Steps to Create Application
Step 1: Create a server folder
mkdir server
Step 2: Initialize the Node application using the following command.
npm init -y
Step 3: Install the required dependencies:
npm install express mongoose cors body-parser
Updated dependencies will look like
"dependencies": {
"body-parser": "^1.20.2",
"cors": "^2.8.5",
"express": "^4.19.2",
"mongoose": "^8.3.2"
}
Folder Structure(Backend)
Example:
// server.js
const express = require("express");
const mongoose = require("mongoose");
const cors = require("cors");
const bodyParser = require("body-parser");
const propertyRoutes = require("./routes/propertyRoutes");
const app = express();
const PORT = process.env.PORT || 5000;
app.use(cors());
app.use(express.json());
app.use(bodyParser.json());
mongoose.connect("mongodb://localhost:27017/real_estate", {
useNewUrlParser: true,
useUnifiedTopology: true,
}).then(() => {
console.log("Connected to MongoDB");
}).catch((error) => {
console.error("MongoDB connection error:", error);
});
app.use("/api/properties", propertyRoutes);
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
// propertyController.js
const Property = require("../models/Property");
exports.createProperty = async (req, res) => {
try {
const { title, description, image, contact, price, area } = req.body;
if (!title || !description || !image || !contact || !price || !area) {
return res.status(400).json({ message: "Incomplete property data" });
}
const newProperty = new Property({
title,
description,
image,
contact,
price,
area,
reviews: [],
});
const savedProperty = await newProperty.save();
res.status(201).json(savedProperty);
} catch (error) {
console.error("Error adding property:", error);
res.status(500).json({ message: "Internal Server Error" });
}
};
exports.getAllProperties = async (req, res) => {
try {
const properties = await Property.find();
res.json(properties);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
exports.getPropertyById = async (req, res) => {
try {
const property = await Property.findById(req.params.id);
if (!property) {
return res.status(404).json({ message: "Property not found" });
}
res.json(property);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
exports.updateProperty = async (req, res) => {
try {
const { title, description, image, contact, price, area } = req.body;
if (!title || !description || !image || !contact || !price || !area) {
return res.status(400).json({ message: "Incomplete property data" });
}
const property = await Property.findByIdAndUpdate(req.params.id, {
title,
description,
image,
contact,
price,
area,
}, { new: true });
if (!property) {
return res.status(404).json({ message: "Property not found" });
}
res.json(property);
} catch (error) {
console.error("Error updating property:", error);
res.status(500).json({ message: "Internal Server Error" });
}
};
exports.deleteProperty = async (req, res) => {
try {
const property = await Property.findByIdAndDelete(req.params.id);
if (!property) {
return res.status(404).json({ message: "Property not found" });
}
res.json({ message: "Property deleted", deletedProperty: property });
} catch (error) {
console.error("Error deleting property:", error);
res.status(500).json({ message: "Internal Server Error" });
}
};
// Property.js
const mongoose = require("mongoose");
const propertySchema = new mongoose.Schema({
title: String,
description: String,
image: String,
contact: String,
price: Number,
area: Number,
reviews: [
{
user: String,
rating: Number,
comment: String,
},
],
});
const Property = mongoose.model("Property", propertySchema);
module.exports = Property;
// propertyRoutes.js
const express = require("express");
const router = express.Router();
const propertyController = require("../controllers/propertyController");
router.post("/", propertyController.createProperty);
router.get("/", propertyController.getAllProperties);
router.get("/:id", propertyController.getPropertyById);
router.put("/:id", propertyController.updateProperty);
router.delete("/:id", propertyController.deleteProperty);
module.exports = router;
To start the server run the following command.
node server.js
Step 4: Set Up the Angular Frontend
Run the following command to install Angular CLI globally:
npm install -g @angular/cli
Step 5: Create Angular Application
ng new client
Folder Structure(Frontend)
Step 6: Install the required dependencies
npm install bootstrap
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",
"bootstrap": "^5.3.3",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.14.3"
}
Creating and managing files
- Inside the /client/src/app/components folder, create the necessary components:property-list.component.ts, add-property.component.ts, property-item.component.ts
- Inside the /client/src/app/services folder create : property.service.ts
- Inside the /client/src/app/models folder, create: property.model.ts
- Inside app create app.module.ts
- update main.ts , styles.css , app.component.html and app.component.ts
Example
<!-- app/app.component.html -->
<h1>Real Estate Management</h1>
<app-add-property (propertyAdded)="onPropertyAdded($event)">
</app-add-property>
<!-- Filter Options -->
<div>
<label for="sortBy">Sort By:</label>
<select
id="sortBy"
[(ngModel)]="filterOptions.sortBy"
(change)="applyFilter()"
>
<option value="">None</option>
<option value="price">Price</option>
<option value="area">Area</option>
</select>
<label for="sortOrder">Sort Order:</label>
<select
id="sortOrder"
[(ngModel)]="filterOptions.sortOrder"
(change)="applyFilter()"
>
<option value="asc">Ascending</option>
<option value="desc">Descending</option>
</select>
</div>
<app-property-list
[properties]="filteredProperties"
(deleteProperty)="onDeleteProperty($event)"
></app-property-list>
/* styles.css */
input {
border: 2px solid black;
}
button {
background-color: #007BFF;
color: #fff;
border: none;
padding: 10px;
cursor: pointer;
border-radius: 4px;
}
button:hover {
background-color: #0056b3;
}
h1,
h2 {
text-align: center;
}
/* Property List component */
.property-list {
display: flex;
flex-wrap: wrap;
gap: 20px;
}
.property-card {
border: 1px solid #ddd;
border-radius: 18px;
padding: 15px;
width: -moz-fit-content;
width: fit-content;
box-shadow: 0 14px 18px rgba(0, 0, 0, 0.1);
background-color: #fff;
margin-bottom: 3rem;
}
.property-card h3 {
margin-bottom: 10px;
}
.property-card button {
background-color: #007BFF;
color: #fff;
border: none;
padding: 8px;
cursor: pointer;
border-radius: 4px;
}
.property-card button:hover {
background-color: #0056b3;
}
img {
height: 200px;
width: 300px;
border-radius: 10px;
margin-bottom: 10px;
}
/* AddProperty component */
.form-container {
max-width: 300px;
margin: 20px auto;
padding: 20px;
background-color: #f7f7f7;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
.form-row {
display: flex;
gap: 20px;
margin-bottom: 15px;
}
.form-row label {
flex: 1;
}
.form-row input {
flex: 2;
padding: 8px;
width: 100%;
box-sizing: border-box;
}
/* GFG style */
.gfg {
background-color: green;
text-align: center;
color: white;
padding: 15px;
border-radius: 10px;
margin-bottom: -20px;
}
.list {
margin-top: -50px;
}
// components/add-property.component.ts
import { Component, OnInit, Output, EventEmitter } from '@angular/core';
import { Property } from '../models/property.model';
import { PropertyService } from '../services/property.service';
@Component({
selector: 'app-add-property',
template: `
<div>
<h2 style="color: #007BFF;">Add a New Property</h2>
<form (submit)="addProperty()">
<div class="form-row">
<label
>Title:
<input
type="text"
[(ngModel)]="newProperty.title"
name="title"
required
/>
</label>
<label
>Description:
<input
type="text"
[(ngModel)]="newProperty.description"
name="description"
required
/>
</label>
</div>
<div class="form-row">
<label
>Image URL:
<input
type="text"
[(ngModel)]="newProperty.image"
name="image"
required
/>
</label>
<label
>Contact:
<input
type="text"
[(ngModel)]="newProperty.contact"
name="contact"
required
/>
</label>
</div>
<div class="form-row">
<label
>Price:
<input
type="number"
[(ngModel)]="newProperty.price"
name="price"
required
/>
</label>
<label
>Area (sq/feet):
<input
type="number"
[(ngModel)]="newProperty.area"
name="area"
required
/>
</label>
</div>
<button type="submit" style="background-color: blue;">
Add Property
</button>
</form>
</div>
`,
styles: [],
})
export class AddPropertyComponent implements OnInit {
newProperty: Property = {
title: '',
description: '',
image: '',
contact: '',
price: 0,
area: 0,
};
@Output() propertyAdded = new EventEmitter<Property>();
constructor(private propertyService: PropertyService) { }
ngOnInit(): void { }
addProperty(): void {
this.propertyService.addProperty(this.newProperty).subscribe(
(res: Property) => {
this.propertyAdded.emit(res);
this.newProperty = {
title: '',
description: '',
image: '',
contact: '',
price: 0,
area: 0,
};
},
(error) => console.error(error)
);
}
}
//components/property-item.component.ts
import { Component, Input } from '@angular/core';
import { Property } from '../models/property.model';
@Component({
selector: 'app-property-item',
template: `
<div>
<h3>{{ property.title }}</h3>
<p>{{ property.description }}</p>
<img [src]="property.image" alt="{{ property.title }}" />
<p>Contact: {{ property.contact }}</p>
</div>
`,
})
export class PropertyItemComponent {
@Input() property!: Property;
constructor() { }
}
//components/property-list.component.ts
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { Property } from '../models/property.model';
import { PropertyService } from '../services/property.service';
@Component({
selector: 'app-property-list',
template: `
<div class="property-list">
<div *ngFor="let property of properties" class="property-card">
<h3>{{ property.title }}</h3>
<p>{{ property.description }}</p>
<p>Price: {{ property.price }}</p>
<p>Area: {{ property.area }} sq/ft</p>
<img [src]="property.image" alt="{{ property.title }}" />
<p>Contact: {{ property.contact }}</p>
<button (click)="onDeleteProperty(property._id)">Delete</button>
</div>
</div>
`,
styles: [
// Styles remain the same
],
})
export class PropertyListComponent implements OnInit {
@Input() properties: Property[] = [];
@Output() deleteProperty = new EventEmitter<string>();
constructor(private propertyService: PropertyService) { }
ngOnInit(): void { }
onDeleteProperty(id: string | undefined): void {
if (id) {
this.propertyService.deleteProperty(id).subscribe(
() => {
this.properties = this.properties.filter(
(property) => property._id !== id
);
},
(error) => console.error(error)
);
}
}
}
// models/property.model.ts
export interface Property {
_id?: string;
title: string;
description: string;
image: string;
contact: string;
price: number;
area: number;
reviews?: Review[];
}
export interface Review {
user: string;
rating: number;
comment: string;
}
//services/property.service.ts
import { Injectable } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { Observable } from 'rxjs';
import { Property } from '../models/property.model';
@Injectable({
providedIn: 'root',
})
export class PropertyService {
private apiUrl = 'http://localhost:5000/api/properties';
constructor(private http: HttpClient) { }
getAllProperties():
Observable<Property[]> {
return this.http.get<Property[]>
(this.apiUrl);
}
addProperty(property: Property):
Observable<Property> {
return this.http.post<Property>
(this.apiUrl, property);
}
deleteProperty(id: string):
Observable<any> {
return this.http
.delete(`${this.apiUrl}/${id}`);
}
addReview(id: string, review: any):
Observable<Property> {
return this.http.post<Property>
(`${this.apiUrl}/${id}/review`, review);
}
getFilteredProperties(params: any):
Observable<Property[]> {
return this.http.get<Property[]>
(this.apiUrl, { params: params });
}
}
// app.component.ts
import { Component, OnInit } from '@angular/core';
import { Property, Review } from './models/property.model'; // Import Review
import { PropertyService } from './services/property.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent implements OnInit {
properties: Property[] = [];
filteredProperties: Property[] = [];
filterOptions = { sortBy: '', sortOrder: 'asc' };
constructor(private propertyService: PropertyService) { }
ngOnInit(): void {
this.getAllProperties();
}
getAllProperties(): void {
this.propertyService.getAllProperties().subscribe(
(res: Property[]) => {
this.properties = res;
this.applyFilter();
},
(error) => console.error(error)
);
}
onPropertyAdded(property: Property): void {
this.properties.push(property);
this.applyFilter();
}
onDeleteProperty(id: string): void {
this.properties = this.properties
.filter((property) => property._id !== id);
this.applyFilter();
}
applyFilter(): void {
this.filteredProperties = this.properties.slice();
if (this.filterOptions.sortBy) {
this.filteredProperties.sort((a, b) => {
if (this.filterOptions.sortBy === 'price') {
return this.filterOptions.sortOrder === 'asc'
? a.price - b.price
: b.price - a.price;
} else if (this.filterOptions.sortBy === 'area') {
return this.filterOptions.sortOrder === 'asc'
? a.area - b.area
: b.area - a.area;
} else {
return 0;
}
});
}
}
}
//app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';
import { FormsModule } from '@angular/forms'; // Import FormsModule
import { AppComponent } from './app.component';
import { AddPropertyComponent } from './components/add-property.component';
import { PropertyListComponent } from './components/property-list.component';
import { PropertyItemComponent } from './components/property-item.component';
import { PropertyService } from './services/property.service';
@NgModule({
declarations: [
AppComponent,
AddPropertyComponent,
PropertyListComponent,
PropertyItemComponent,
],
imports: [
BrowserModule,
HttpClientModule,
FormsModule,
],
providers: [PropertyService],
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';
enableProdMode();
platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.error(err));
To start the frontend run the following command.
ng serve