Community Forum Page using MERN Stack
In the ever-expanding digital landscape, fostering meaningful connections within communities is paramount. The Community Forum Page project, developed using the MERN (MongoDB, Express, React, Node) stack, aims to provide a dynamic platform for users to engage in discussions, share valuable information, and cultivate a thriving community environment.
Preview of final output: Let us have a look at how the final output will look like.
Prerequisites:
Approach to create Community Forum Page:
- Creating BackEnd for our Application
- Creating FrontEnd for Application
- Connecting It with MongoDB
Steps to Create the Backend:
Step 1: Create a directory for project
npm init backend
Step 2: Open project using the command
cd backend
Step 3: Installing the required packages
npm install express mongoose cors body-parser
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",
}
Explanation:
- Imports: Express, Mongoose, body-parser, cors modules for web server, MongoDB connection, request body handling, and CORS.
- Creates an Express app instance, specifies a port (from PORT environment variable or defaulting to 5000).
- Middleware setup: Handles CORS and parses JSON request bodies using cors and body-parser.
- Defines “Request” data structure with attributes like residentName, content, and likes using Mongoose.
- API Endpoints: Create, Read, Update (like), and Delete operations for requests are defined.
- Server start: The server starts, listens on the specified port, and logs an operational message once running.
Example: Create server.js (Express Server) as connection between frontEnd and BackEnd. Insert the following code to it :
Javascript
const express = require( 'express' ); const mongoose = require( 'mongoose' ); const bodyParser = require( 'body-parser' ); const cors = require( 'cors' ); const app = express(); const PORT = process.env.PORT || 5000; app.use(cors()); app.use(bodyParser.json()); mongoose.connect( 'mongodb://localhost:27017/societyDB' , { useNewUrlParser: true , useUnifiedTopology: true }); const requestSchema = new mongoose.Schema({ residentName: { type: String, required: true }, content: { type: String, required: true }, likes: { type: Number, default : 0 }, }); const Request = mongoose.model( 'Request' , requestSchema); // Create a new request app.post( '/requests' , async (req, res) => { try { const { residentName, content } = req.body; const newRequest = new Request({ residentName, content }); const savedRequest = await newRequest.save(); res.json(savedRequest); } catch (error) { res.status(500) .json({ error: 'Internal Server Error' }); } }); // Get all requests app.get( '/requests' , async (req, res) => { try { const requests = await Request.find(); res.json(requests); } catch (error) { res.status(500) .json( { error: 'Internal Server Error' } ); } }); // Like a request app.patch( '/requests/:id/like' , async (req, res) => { try { const { id } = req.params; const updatedRequest = await Request.findByIdAndUpdate( id, { $inc: { likes: 1 } }, { new : true }); res.json(updatedRequest); } catch (error) { res.status(500) .json( { error: 'Internal Server Error' } ); } }); // Delete a request app. delete ( '/requests/:id' , async (req, res) => { try { const { id } = req.params; await Request.findByIdAndDelete(id); res.json( { message: 'Request deleted successfully' } ); } catch (error) { res.status(500) .json( { error: 'Internal Server Error' } ); } }); // Example: routes/requests.js const router = express.Router(); app.listen(PORT, () => console.log( `Server is running on port ${PORT}` ) ); |
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: Installing the required packages:
npm i axios react-router-dom
Project Structure:
The updated dependencies in package.json for frontend will look like:
"dependencies": {
"axios": "^1.6.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.17.0",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
}
Explanation:
- Form component with a title (FormTitle) indicating it’s for creating requests.
- Utilizes flexbox with a column direction and small gaps for a clean layout.
- Includes two form fields: Resident Name (text input) and Request Content (textarea), both styled.
- Utilizes the useState hook to manage state for residentName and content.
- handleSubmit function prevents default form submission, creates a new request object, calls onAddRequest callback with the new request, and clears form fields.
- Receives onAddRequest prop as a callback function for handling new request additions.
- Uses axios for asynchronous GET request to http://localhost:5000/requests, storing data in the requests state.
- handleLike function makes a PATCH request to update likes for a specific request, then refreshes the request list.
- UI divided into two columns: left column has RequestForm for new requests, and right column displays a list of community requests.
- Community requests show resident name, issue content, and a “Like” button for user interaction.
Javascript
// src/App.js import React from "react" ; import { BrowserRouter as Router, Route, Routes } from "react-router-dom" ; import RequestList from "./components/RequestList" ; import RequestForm from "./components/RequestForm" ; import About from "./components/About" ; import Navbar from "./components/Navbar" ; const App = () => { return ( <Router> <Navbar /> <Routes> <Route path= "/" element={<RequestList />} /> <Route path= "/about" element={<About />} /> </Routes> </Router> ); }; export default App; |
Javascript
// src/components/RequestList.js import React, { useEffect, useState } from 'react' ; import axios from 'axios' ; // Import the RequestForm component import RequestForm from './RequestForm' ; import './RequestList.css' ; const RequestList = () => { const [requests, setRequests] = useState([]); const fetchRequests = async () => { try { const response = await axios.get( 'http://localhost:5000/requests' ); setRequests(response.data); } catch (error) { console.error( 'Error fetching requests:' , error); } }; useEffect(() => { fetchRequests(); }, []); const handleLike = async (id) => { try { await axios.patch(` http: //localhost:5000/requests/${id}/like `); // Refresh the list of requests after liking fetchRequests(); } catch (error) { console.error( 'Error liking request:' , error); } }; return ( <div className= "request-list-container" > <RequestForm onAddRequest={ (newRequest) => setRequests([newRequest, ...requests]) } /> <ul className= "request-list" > {requests.map((request) => ( <div key={request._id} className= "request-item" > <p className= "resident-name" > {request.residentName} </p> <p className= "request-content" > {request.content} </p> <p className= "likes" > Likes: {request.likes} </p> <button className= "like-button" onClick= { () => handleLike(request._id) }> Like </button> </div> ))} </ul> </div> ); }; export default RequestList; |
Javascript
// src/components/RequestForm.js import React, { useState } from 'react' ; import axios from 'axios' ; import './RequestForm.css' ; // Import the CSS file for styling const RequestForm = ({ onAddRequest }) => { const [residentName, setResidentName] = useState( '' ); const [content, setContent] = useState( '' ); const handleSubmit = async (e) => { e.preventDefault(); try { const response = await axios.post( 'http://localhost:5000/requests' , { residentName, content, }); // Assuming the backend returns the newly created request onAddRequest(response.data); setResidentName( '' ); setContent( '' ); } catch (error) { console.error( 'Error creating request:' , error); } }; return ( <div className= "request-form-container" > <h2>Create a Request</h2> <form onSubmit={handleSubmit}> <div className= "form-group" > <label htmlFor= "residentName" > Resident Name: </label> <input id= "residentName" type= "text" value={residentName} onChange={ (e) => setResidentName(e.target.value) } /> </div> <div className= "form-group" > <label htmlFor= "content" > Content: </label> <textarea id= "content" value={content} onChange={ (e) => setContent(e.target.value) } /> </div> <button type= "submit" className= "submit-button" > Submit Request </button> </form> </div> ); }; export default RequestForm; |
Javascript
// src/components/About.js import React from 'react' ; import styled from 'styled-components' ; const AboutContainer = styled.div` max-width: 800px; margin: 20px auto; padding: 20px; background-color: #fff; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); border-radius: 8px; `; const Title = styled.h2` color: #333; margin-bottom: 20px; `; const Paragraph = styled.p` line-height: 1.6; margin-bottom: 10px; `; const About = () => { return ( <AboutContainer> <Title>About Community Forum</Title> <Paragraph> Welcome to the Community Forum, a place where residents can connect, share ideas, and support each other. This platform allows you to raise requests, express your opinions, and engage in discussions with fellow community members. </Paragraph> <Paragraph> We believe in fostering a sense of community and collaboration. Feel free to explore the forum, like and contribute to requests, and be an active participant in shaping our community. </Paragraph> { /* Add more information as needed */ } </AboutContainer> ); }; export default About; |
Javascript
// src/components/Navbar.js import React from 'react' ; import styled from 'styled-components' ; import { Link } from 'react-router-dom' ; const NavbarContainer = styled.div` background-color: #212e3d; padding: 15px; display: flex; justify-content: space-between; align-items: center; `; const Logo = styled.h1` color: #fff; margin: 0; `; const NavLink = styled(Link)` color: #fff; text-decoration: none; margin-left: 20px; font-weight: bold; transition: color 0.3s ease- in -out; &:hover { color: #0e87ea; } `; const Navbar = () => { return ( <NavbarContainer> <Logo>Community Forum</Logo> <div> <NavLink to= "/" >Home</NavLink> <NavLink to= "/about" >About</NavLink> { /* Add more navigation links as needed */ } </div> </NavbarContainer> ); }; export default Navbar; |
CSS
/* src/components/RequestForm.css */ .request-form-container { width : 600px ; padding : 100px ; border : 1px solid #ddd ; border-radius: 8px ; background-color : #f9f9f9 ; margin : 2% ; } .form-group { margin-bottom : 15px ; } label { display : block ; font-weight : bold ; margin-bottom : 5px ; } input, textarea { width : 100% ; padding : 8px ; border : 1px solid #ccc ; border-radius: 4px ; } .submit-button { background-color : #4caf50 ; color : #fff ; padding : 10px ; border : none ; border-radius: 4px ; cursor : pointer ; } |
CSS
/* src/components/RequestList.css */ .request-list-container { padding : 20px ; display : flex; flex- direction : row; border : 1px solid #ddd ; border-radius: 8px ; background-color : #f9f9f9 ; } .request-list { display : flex; flex-wrap: wrap; flex- direction : row; margin : 2% ; } .request-item { margin : 1% ; padding : 10px ; width : 400px ; border : 1px solid #ccc ; border-radius: 4px ; background-color : #fff ; } .resident-name { font-weight : bold ; margin-bottom : 5px ; } .request-content { margin-bottom : 10px ; } .likes { color : #888 ; } .like-button { background-color : #4caf50 ; color : #fff ; padding : 5px 10px ; border : none ; border-radius: 4px ; cursor : pointer ; } |
Steps to run the Application:
node server.js and npm start
Output:
Output of Data Saved in Database:
Contact Us