Social Media Platform using MERN Stack
In web development, creating a “Social Media Website” will showcase and utilising the power of MERN stack – MongoDB, Express, React, and Node. This application will provide users the functionality to add a post, like the post and able to comment on it.
Preview Image: Let us have a look at how the final output will look like.
Prerequisites:
Approach to create Social Media Platform:
- The social media website was developed with a dual focus on backend using Express.js and frontend using React.
- Express handled API routes for CRUD operations, including likes and comments, and Multer facilitated file uploads for multimedia content.
- React was chosen for the frontend, providing an interactive user interface with components for posts, likes, and comments.
- Axios played a pivotal role in connecting the frontend to the backend API endpoints, ensuring smooth communication.
- The integration phase involved configuring CORS, connecting frontend to backend API URLs, and thorough end-to-end testing for a seamless user experience.
Steps to Create the Project:
Step 1: Create a directory for the backend by running the following command.
npm init social_backend
cd social_backend
Step 2: Initialize the Express project and install the following dependencies.
npm init -y
npm install express mongoose cors body-parser multer uuid
Folder Structure(Backend):
The updated dependencies in package.json file of backend will look like:
"dependencies": {
"body-parser": "^1.20.2",
"cors": "^2.8.5",
"express": "^4.18.2",
"mongoose": "^8.0.3",
"multer": "^1.4.5-lts.1",
"uuid": "^9.0.1"
}
Example: Create the required files and add the following code:
Javascript
// models/Post.js const mongoose = require( 'mongoose' ); const postSchema = new mongoose.Schema({ title: String, content: String, likes: { type: Number, default : 0 }, comments: [{ text: String }], }); const Post = mongoose.model( 'Post' , postSchema); module.exports = Post; |
Javascript
// server.js const express = require( 'express' ); const mongoose = require( 'mongoose' ); const bodyParser = require( 'body-parser' ); const cors = require( 'cors' ); const multer = require( 'multer' ); const path = require( 'path' ); const app = express(); const PORT = process.env.PORT || 5000; app.use(cors()); app.use( '/uploads' , express.static(path.join(__dirname, 'uploads' ))); const storage = multer.diskStorage({ destination: function (req, file, cb) { cb( null , 'uploads/' ); }, filename: function (req, file, cb) { cb( null , file.fieldname + '-' + Date.now() + path.extname(file.originalname)); } }); const upload = multer({ storage: storage }); mongoose.connect( 'Your MongoDB connection string' , { useNewUrlParser: true , useUnifiedTopology: true }); const postSchema = new mongoose.Schema({ title: String, content: String, file: String, likes: { type: Number, default : 0 }, comments: [{ text: String }], }); const Post = mongoose.model( 'Post' , postSchema); app.use(bodyParser.json()); app.get( '/api/posts' , async (req, res) => { try { const posts = await Post.find(); res.json(posts); } catch (error) { res.status(500).json({ error: 'Internal Server Error' }); } }); app.post( '/api/posts' , upload.single( 'file' ), async (req, res) => { try { const { title, content } = req.body; const file = req.file ? req.file.filename : undefined; if (!title || !content) { return res.status(400).json({ error: 'Title and content are required fields' }); } const post = new Post({ title, content, file }); await post.save(); res.status(201).json(post); } catch (error) { console.error( 'Error creating post:' , error); res.status(500).json({ error: 'Internal Server Error' }); } }); app.post( '/api/posts/like/:postId' , async (req, res) => { try { const postId = req.params.postId; const post = await Post.findById(postId); if (!post) { return res.status(404).json({ error: 'Post not found' }); } post.likes += 1; await post.save(); res.json(post); } catch (error) { console.error( 'Error liking post:' , error); res.status(500).json({ error: 'Internal Server Error' }); } }); app.post( '/api/posts/comment/:postId' , async (req, res) => { try { const postId = req.params.postId; const { text } = req.body; const post = await Post.findById(postId); if (!post) { return res.status(404).json({ error: 'Post not found' }); } post.comments.push({ text }); await post.save(); res.json(post); } catch (error) { console.error( 'Error adding comment:' , error); res.status(500).json({ error: 'Internal Server Error' }); } }); app.listen(PORT, () => { console.log(`Server is running on port ${PORT}`); }); |
Step 3: To start the backend run the following command.
node server.js
Step 4: Set up React frontend using the command.
npx create-react-app social_frontend
cd social_frontend
Step 5 : Installing the required packages:
npm i axios react-router-dom
Folder Structure(Frontend):
The updated dependencies in package.json file of frontend will look lik:
"dependencies": {
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"axios": "^1.6.5",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.21.1",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
}
Example: Create the required files and the following code.
Javascript
// App.js import React from 'react' ; import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom' ; import Home from './Home' ; import CreatePost from './CreatePost' ; import './App.css' ; function App() { return ( <Router> <div className= "app" > <nav> <ul> <li> <Link to= "/" >Home</Link> </li> <li> <Link to= "/create" >Create Post</Link> </li> </ul> </nav> <Routes> <Route path= "/create" element={<CreatePost />} /> <Route path= "/" element={<Home />} /> </Routes> </div> </Router> ); } export default App; |
Javascript
// Home.js import React, { useState, useEffect } from "react" ; import axios from "axios" ; function Home() { const [commentInput, setCommentInput] = useState( "" ); const [posts, setPosts] = useState([]); useEffect(() => { axios .get( "http://localhost:5000/api/posts" ) .then((response) => setPosts(response.data)) . catch ((error) => console.error( "Error fetching posts:" , error)); }, []); const handleLike = (postId) => { axios .post(`http: //localhost:5000/api/posts/like/${postId}`) .then((response) => { const updatedPosts = posts.map((post) => post._id === postId ? response.data : post ); setPosts(updatedPosts); }) . catch ((error) => console.error( "Error liking post:" , error)); }; const handleAddComment = (postId, commentText) => { axios .post(`http: //localhost:5000/api/posts/comment/${postId}`, { text: commentText, }) .then((response) => { const updatedPosts = posts.map((post) => post._id === postId ? response.data : post ); setPosts(updatedPosts); }) . catch ((error) => console.error( "Error adding comment:" , error)); }; return ( <div className= "home" > <h2>Recent Posts</h2> {posts.map((post) => ( <div key={post._id} className= "post" > <h3>{post.title}</h3> <p>{post.content}</p> {post.file && ( <div> {post.file.includes( ".mp4" ) ? ( <video width= "320" height= "240" controls> <source src={ `http: //localhost:5000/uploads/${post.file}` } type= "video/mp4" /> Your browser does not support the video tag. </video> ) : ( <img src={ `http: //localhost:5000/uploads/${post.file}` } alt= "Post Media" /> )} </div> )} <p>Likes: {post.likes}</p> <button onClick={() => handleLike(post._id)}>Like</button> <p>Comments: {post.comments.length}</p> <ul> {post.comments.map((comment, index) => ( <li key={index}>{comment.text}</li> ))} </ul> <input type= "text" placeholder= "Add a comment" className= "comment-input" onChange={(e) => setCommentInput(e.target.value)} /> <button onClick={() => handleAddComment(post._id, commentInput)} className= "comment-button" > Add Comment </button> </div> ))} </div> ); } export default Home; |
Javascript
// CreatePost.js import React, { useState } from "react" ; import axios from "axios" ; function CreatePost() { const [newPost, setNewPost] = useState({ title: "" , content: "" , file: null , }); const handleInputChange = (event) => { const { name, value } = event.target; setNewPost({ ...newPost, [name]: value }); }; const handleFileChange = (event) => { setNewPost({ ...newPost, file: event.target.files[0] }); }; const handlePostSubmit = () => { const formData = new FormData(); formData.append( "title" , newPost.title); formData.append( "content" , newPost.content); formData.append( "file" , newPost.file); axios .post( "http://localhost:5000/api/posts" , formData) .then((response) => { setNewPost({ title: "" , content: "" , file: null }); }) . catch ((error) => console.error( "Error creating post:" , error)); }; return ( <div className= "create-post" > <h2>Create a Post</h2> <input type= "text" name= "title" placeholder= "Title" value={newPost.title} onChange={handleInputChange} /> <textarea name= "content" placeholder= "Content" value={newPost.content} onChange={handleInputChange} ></textarea> <input type= "file" name= "file" onChange={handleFileChange} /> <button onClick={handlePostSubmit}>Post</button> </div> ); } export default CreatePost; |
CSS
/* App.css */ .home { max-width : 800px ; margin : 0 auto ; } .post { border : 1px solid #ddd ; padding : 15px ; margin-bottom : 20px ; } .post h 3 { color : #333 ; } .post p { color : #555 ; } /* App.css */ .create-post { max-width : 600px ; margin : 20px auto ; padding : 20px ; border : 1px solid #ddd ; background-color : #fff ; box-shadow: 0 0 10px rgba( 0 , 0 , 0 , 0.1 ); } .create-post h 2 { color : #333 ; } .create-post input, .create-post textarea { width : 100% ; margin : 10px 0 ; padding : 10px ; } .create-post button { background-color : #4caf50 ; color : #fff ; padding : 10px 15px ; border : none ; cursor : pointer ; } .comment-input { margin-top : 10px ; padding : 8px ; width : 70% ; } .comment-button { background-color : #4caf50 ; color : #fff ; padding : 8px 16px ; border : none ; cursor : pointer ; } .post img, .post video { max-width : 100% ; height : auto ; margin-top : 10px ; } .post button { background-color : #4caf50 ; color : #fff ; padding : 8px 16px ; border : none ; cursor : pointer ; margin-right : 10px ; } .post ul { list-style : none ; padding : 0 ; } .post li { margin-bottom : 5px ; } .comment-input { margin-top : 10px ; padding : 8px ; width : 70% ; } .comment-button { background-color : #4caf50 ; color : #fff ; padding : 8px 16px ; border : none ; cursor : pointer ; } /* App.css */ .app { max-width : 800px ; margin : 0 auto ; } nav { background-color : #333 ; padding : 10px ; } nav ul { list-style : none ; margin : 0 ; padding : 0 ; } nav li { display : inline- block ; margin-right : 20px ; } nav a { text-decoration : none ; color : #fff ; font-weight : bold ; font-size : 16px ; } nav a:hover { color : #4caf50 ; } .create-post, .home { border : 1px solid #ddd ; padding : 20px ; margin-bottom : 20px ; background-color : #fff ; box-shadow: 0 0 10px rgba( 0 , 0 , 0 , 0.1 ); } .home h 2 , .create-post h 2 { color : #333 ; } .home .post, .create-post { margin-bottom : 30px ; } .home .post button, .create-post button { background-color : #4caf50 ; color : #fff ; padding : 10px 15px ; border : none ; cursor : pointer ; } .home .post button:hover, .create-post button:hover { background-color : #45a049 ; } |
Step 6: Start the application by running the following command.
npm start
Output:
Contact Us