Real Estate Management using MERN
In this article, we will guide you through the process of building a Real Estate Management Application using the MERN stack. MERN stands for MongoDB, Express, React, and Node. MongoDB will serve as our database, Express will handle the backend, React will create the frontend, and Node.js will be the runtime for our server.
Preview of final output: Let us have a look at how the final application will look like.
Prerequisites:
Approach to create Real Estate Management Website using MERN:
- Import Statements:
- Import necessary dependencies and components.
- React is imported for defining React components.
useState
anduseEffect
are imported from React for managing state and handling side effects respectively.PropertyList
andAddProperty
are imported, assumed to be custom components.- Axios is imported for making HTTP requests.
- Functional Component:
- Define a functional component named
App
.
- Define a functional component named
- State Management:
- Use the
useState
hook to manage the state ofproperties
, which is initialized as an empty array. - The
properties
state will hold the list of properties fetched from the server.
- Use the
- Data Fetching:
- Use the
useEffect
hook to make an HTTP GET request to ‘http://localhost:5000/api/properties‘ when the component mounts. - Update the
properties
state with the data received from the server.
- Use the
- Event Handlers:
- Define event handlers:
handleAddProperty
: Takes a new property as an argument and adds it to theproperties
state using thesetProperties
function.handleContactOwner
: Takes contact information as an argument and displays an alert with the contact information.handleDeleteProperty
: Takes a property ID as an argument, makes an HTTP DELETE request to delete the property, and then updates the state by filtering out the deleted property.
- Define event handlers:
- Component Rendering:
- Render a div containing:
- Two heading elements (
h1
) with class and inline styles. - The
AddProperty
component, passing thehandleAddProperty
function as a prop. - The
PropertyList
component, passing thehandleDeleteProperty
andhandleContactOwner
functions, as well as theproperties
state.
- Two heading elements (
- Render a div containing:
Steps to Create the Backend:
Step 1: Create a directory for project
npm init backend
Step 2: Open Restaurant-Recommendation using the command
cd backend
Step 3: Installing the required packages
npm install express mongoose cors body-parser
This will install Express.js for the backend, Mongoose for MongoDB integration, CORS for handling cross-origin requests, and Body Parser for parsing incoming JSON requests.
Project Structure:
The updated dependencies in package.json file for backend will look like:
"dependencies": {
"body-parser": "^1.20.2",
"cors": "^2.8.5",
"express": "^4.18.2",
"mongoose": "^8.0.0",
}
Example: Create a file named `server.js` in server folder. This file will serve as our backend server.
Javascript
const express = require( "express" ); const mongoose = require( "mongoose" ); const cors = require( "cors" ); const bodyParser = require( "body-parser" ); const app = express(); const PORT = process.env.PORT || 5000; app.use(cors()); app.use(express.json()); app.use(bodyParser.json()); // Connect to MongoDB mongoose .connect( "mongodb://localhost:27017/real_estate" , { useNewUrlParser: true , useUnifiedTopology: true , }) .then(() => { console.log( "Connected to MongoDB" ); }); // Define the Property schema const propertySchema = new mongoose.Schema({ title: String, description: String, image: String, contact: String, reviews: [ { user: String, rating: Number, comment: String, }, ], }); const Property = mongoose.model( "Property" , propertySchema); // API endpoints // Route to add a new property app.post( "/api/properties" , async (req, res) => { try { const { title, description, image, contact } = req.body; // Validate request data if (!title || !description || !image || !contact) { return res .status(400) .json({ message: "Incomplete property data" }); } // Create a new property const newProperty = new Property({ title, description, image, contact, reviews: [], }); // Save the new property to the database const savedProperty = await newProperty.save(); // Respond with the newly added property res.status(201).json(savedProperty); } catch (error) { console.error( "Error adding property:" , error); res.status(500).json({ message: "Internal Server Error" }); } }); // Route to get all properties app.get( "/api/properties" , async (req, res) => { try { const properties = await Property.find(); res.json(properties); } catch (error) { res.status(500).json({ message: error.message }); } }); // Route to add a review for a property app.post( "/api/properties/:id/review" , async (req, res) => { const { user, rating, comment } = req.body; try { const property = await Property.findById(req.params.id); property.reviews.push({ user, rating, comment }); await property.save(); res.status(201).json(property); } catch (error) { res.status(400).json({ message: error.message }); } }); // Delete a property by ID app. delete ( "/api/properties/:id" , async (req, res) => { const propertyId = req.params.id; try { // Find the property by ID and delete it from the database const deletedProperty = await Property.findByIdAndDelete(propertyId); if (!deletedProperty) { return res.status(404).json({ message: "Property not found" }); } res.json({ message: "Property deleted" , deletedProperty }); } catch (error) { console.error( "Error deleting property:" , error); res.status(500).json({ message: "Internal Server Error" }); } }); app.listen(PORT, () => { console.log(`Server is running on port ${PORT}`); }); |
Step 3: Defining the Property Schema and API Endpoints
Continue the `server.js` file by defining the Property schema and implementing API endpoints for adding properties, getting all properties, adding reviews, and deleting properties.
This is already implemented in Step 2.
// Define the Property schema
const propertySchema = new mongoose.Schema({
title: String,
description: String,
image: String,
contact: String,
reviews: [
{
user: String,
rating: Number,
comment: String,
},
],
});
const Property = mongoose.model('Property', propertySchema);
Steps to Create the Frontend:
Step 1: Set up React frontend using the command.
npx create-react-app client
Step 2: Navigate to the project folder using the command.
cd client
Step 3: Install axios
npm i axios
Project Structure:
The updated dependencies in package.json for frontend will look like:
"dependencies": {
"axios": "^1.5.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
}
Example: Below is the client code.
Javascript
// src/App.js import React, { useState, useEffect } from "react" ; import PropertyList from "./components/PropertyList" ; import AddProperty from "./components/AddProperty" ; import axios from "axios" ; import "./App.css" ; const App = () => { const [properties, setProperties] = useState([]); useEffect(() => { axios .get( "http://localhost:5000/api/properties" ) .then((response) => setProperties(response.data)) . catch ((error) => console.error(error)); }, []); // Empty dependency array to fetch properties once on mount const handleAddProperty = (newProperty) => { setProperties((prevProperties) => [...prevProperties, newProperty]); }; const handleContactOwner = (contact) => { alert(`Contacting the owner of property is ${contact}`); }; const handleDeleteProperty = (propertyId) => { axios . delete (`http: //localhost:5000/api/properties/${propertyId}`) .then((response) => { // Filter out the deleted property from the state setProperties((prevProperties) => prevProperties.filter( (property) => property._id !== propertyId ) ); }) . catch ((error) => console.error(error)); }; return ( <div style={{}}> <h1 className= "gfg" style={{ margin: "10px 10px" }}> GFG </h1> <h1 style={{ marginTop: "10px" }}>Real Estate Management</h1> <div> <AddProperty onAddProperty={handleAddProperty} /> <PropertyList onDeleteProperty={handleDeleteProperty} properties={properties} onContactOwner={handleContactOwner} /> </div> </div> ); }; export default App; |
Javascript
//components/ProductList.js import React, { useContext, useEffect, useState } from "react" ; import ProductItem from "./ProductItem" ; import { itemContext } from "../context/itemContext" ; import "../App.css" ; const ProductList = () => { const { products } = useContext(itemContext); const [sortedProducts, setSortedProducts] = useState([...products]); // Keep a local state for sorted products const [minPrice, setMinPrice] = useState(0); const [maxPrice, setMaxPrice] = useState(3000); const [selectedType, setSelectedType] = useState( "all" ); // 'all' represents no type filter useEffect(() => { setSortedProducts([...products]); }, [products]); const handleSortByPrice = () => { const sorted = [...sortedProducts].sort((a, b) => a.price - b.price); setSortedProducts(sorted); }; const handleFilterByPriceRange = () => { const filtered = products.filter( (product) => product.price >= minPrice && product.price <= maxPrice ); setSortedProducts(filtered); }; const handleFilterByType = () => { if (selectedType === "all" ) { // Reset the type filter setSortedProducts([...products]); } else { const filtered = products.filter( (product) => product.type === selectedType ); setSortedProducts(filtered); } }; return ( <div className= "prdt-list" > <h2>Product List</h2> <div className= "filter-btn" > <button onClick={handleSortByPrice}>Sort by Price</button> <label> Min Price: <input type= "number" value={minPrice} onChange={(e) => setMinPrice(Number(e.target.value))} /> </label> <label> Max Price: <input type= "number" value={maxPrice} onChange={(e) => setMaxPrice(Number(e.target.value))} /> </label> <button onClick={() => handleFilterByPriceRange()}> Filter by Price Range </button> <label> Filter by Type: <select value={selectedType} onChange={(e) => setSelectedType(e.target.value)} > <option value= "all" >All</option> <option value= "Fruit" >Fruit</option> <option value= "Vegetable" >Vegetable</option> { /* Add more options as needed */ } </select> </label> <button onClick={handleFilterByType}>Filter by Type</button> </div> <ul className= "item-card" > {sortedProducts.map((product) => ( <ProductItem key={product._id} product={product} /> ))} </ul> <div className= "buy-now-btn" >Buy Now</div> </div> ); }; export default ProductList; |
Javascript
// src/components/AddProperty.js import React, { useState } from 'react' ; import axios from 'axios' ; import '../App.css' const AddProperty = ({ onAddProperty }) => { const [newProperty, setNewProperty] = useState({ title: '' , description: '' , image: '' , contact: '' , }); const handleAddProperty = () => { // Submit a new property axios.post( 'http://localhost:5000/api/properties' , newProperty) .then(response => { // Notify the parent component about the new property onAddProperty(response.data); // Clear the newProperty state for the next entry setNewProperty({ title: '' , description: '' , image: '' , contact: '' , }); }) . catch (error => console.error(error)); }; return ( <div> <h2 style={{ "color" : "#007BFF" }}> Add a New Property </h2> <form onSubmit={(e) => { e.preventDefault(); handleAddProperty(); }}> <div className= "form-row" > <label>Title: <input type= "text" value={newProperty.title} onChange={(e) => setNewProperty( { ...newProperty, title: e.target.value })} required /> </label> <label>Description: <input type= "text" value={newProperty.description} onChange={ (e) => setNewProperty( { ...newProperty, description: e.target.value })} required /> </label> </div> <div className= "form-row" > <label>Image URL: <input type= "text" value={newProperty.image} onChange={ (e) => setNewProperty( { ...newProperty, image: e.target.value })} required /> </label> <label>Contact: <input type= "text" value={newProperty.contact} onChange={ (e) => setNewProperty( { ...newProperty, contact: e.target.value })} required /> </label> </div> <button type= "submit" style={{ backgroundColor: "blue" }}> Add Property </button> </form> </div> ); }; export default AddProperty; |
CSS
input { border : 2px solid black ; } .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 : 3 rem; } .property-card h 3 { margin-bottom : 10px ; } .property-card button { background-color : #007BFF ; color : #fff ; border : none ; padding : 8px ; cursor : pointer ; border-radius: 4px ; } button { background-color : #007BFF ; } .property-card button:hover { background-color : #0056b3 ; } img { height : 200px ; width : 300px ; border-radius: 10px ; margin-bottom : 10px ; } .list-container { display : flex; flex- direction : row; justify- content : center ; gap: 15px ; } h 1 , h 2 { text-align : center ; } f /* src/components/AddProperty.css */ .property-card { border : 1px solid #ddd ; border-radius: 10px ; box-shadow: 0 4px 8px rgba( 0 , 0 , 0 , 0.1 ); padding : 20px ; margin : 20px ; width : 300px ; } .property-card h 2 { margin-bottom : 15px ; color : #333 ; } .property-card label { display : block ; margin-bottom : 10px ; color : #333 ; } .property-card input { width : 100% ; padding : 8px ; margin-bottom : 10px ; box-sizing: border-box; } .property-card button { background-color : #007BFF ; color : #fff ; border : none ; padding : 10px ; cursor : pointer ; border-radius: 4px ; } .property-card button:hover { background-color : #0056b3 ; } /* AddProperty.css */ .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; } button { background-color : #007BFF ; color : #fff ; border : none ; padding : 10px ; cursor : pointer ; border-radius: 4px ; } button:hover { background-color : #0056b3 ; } form { padding : 10px ; } .gfg { background-color : green ; text-align : center ; color : white ; padding : 15px ; border-radius: 10px ; margin-bottom : -20px ; } .list { margin-top : -50px ; } |
Sep to Start Server and React App:
- To start backend server: Go to server folder inside terminal and write below command
node server.js
- To start frontend server: Go to client folder inside terminal and write below command
npm start
Output:
Output of Data saved in Database: