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:
// 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; |
// 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.
// 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;
|
// Home.js import React, { useState, useEffect } from "react" ;
import axios from "axios" ;
function Home() {
const [commentInput, setCommentInput] = useState( "" );
const [posts, setPosts] = useState([]);
useEffect(() => {
axios
.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;
|
// 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
.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;
|
/* 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: