Social Networking Platform using Next.js
The Social Networking Platform built with NextJS is a web application that provides users the functionality to add a post, like a post, and be able to comment on it. The power of NextJS, a popular React framework for building server-side rendered (SSR) and statically generated web applications, this platform offers a seamless user experience with fast loading times and smooth navigation.
Output Preview: Let us have a look at how the final output will look like
Prerequisites:
Approach to Create Social Networking Platform with NextJS:
- Home Page: Displays recent posts fetched from the backend API.
- Post Interaction: Users can like posts and add comments.
- Create Post: Users can create new posts with a title, content, and optional image upload.
- Backend API: Provides endpoints for fetching recent posts, creating posts, liking posts, and adding comments. Uses MongoDB as the database.
Steps to Create the NextJS App:
Step 1: Set up a NextJS project using the following command.
npx create-next-app next-mern-project
cd next-mern-project
Step 2: Install required dependencies.
npm install axios mongoose
Project Structure:
The updated dependencies in package.json file will look like:
"dependencies": {
"axios": "^1.6.8",
"mongodb": "^6.5.0",
"mongoose": "^8.2.2",
"multer": "^1.4.5-lts.1",
"next": "14.1.3",
"next-connect": "^1.0.0",
"react": "^18",
"react-dom": "^18",
"uuid": "^9.0.1"
}
Step 3: Creating a required files.
- Create components folder with Post.js
- Inside api create file posts.js
- inside pages create file _app.js,index.js and create.js
- create globals.css inside style folder
Example: Below is an example of creating a Social Networking Platform with NextJS.
/* globals.css */
.home {
max-width: 800px;
margin: 0 auto;
}
.post {
border: 1px solid #ddd;
padding: 15px;
margin-bottom: 20px;
}
.post h3 {
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 h2 {
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 h2,
.create-post h2 {
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;
}
//pages/index.js
import React, { useState, useEffect } from 'react';
import axios from 'axios';
function Home() {
const [posts, setPosts] = useState([]);
const [commentText, setCommentText] = useState('');
useEffect(() => {
axios
.get('/api/posts')
.then((response) => setPosts(response.data))
.catch((error) => console.error(
'Error fetching posts:', error));
}, []);
const handleLike = async (id) => {
try {
const response = await axios.put('/api/posts', {
id, action: 'like'
});
setPosts(posts.map(
(post) => (post._id === id ? response.data : post)));
} catch (error) {
console.error('Error liking post:', error);
}
};
const handleComment = async (id, text) => {
try {
const response = await axios.put('/api/posts', {
id, action: 'comment', text
});
setPosts(posts.map(
(post) => (post._id === id ? response.data : post)));
setCommentText(''); // Reset comment text after posting
} catch (error) {
console.error('Error commenting on post:', error);
}
};
const convertImageToBase64 = async (file) => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result);
reader.onerror = (error) => reject(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>
<img src={post.file} alt="Post" />
</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>
<div>
<input type="text" placeholder="Write a comment..."
value={commentText}
onChange={(e) => setCommentText(e.target.value)} />
<button
onClick={() => handleComment(post._id, commentText)}>
Comment
</button>
</div>
</div>
))}
</div>
);
}
export default Home;
// pages/_app.js
import '../styles/globals.css';
import React from 'react';
import { useRouter } from 'next/router';
function MyApp({ Component, pageProps }) {
const router = useRouter();
return (
<div className="app">
<nav>
<ul>
<li>
<button onClick={() => router.push('/')}>
Home
</button>
</li>
<li>
<button onClick={() => router.push('/create')}>
Create Post
</button>
</li>
</ul>
</nav>
<Component {...pageProps} />
</div>
);
}
export default MyApp;
// pages/create.js
import React, { useState } from 'react';
import axios from 'axios';
function CreatePost() {
const [newPost, setNewPost] = useState({
title: '',
content: '',
file: null
});
const [error, setError] = useState('');
const handleInputChange = event => {
const { name, value } = event.target;
setNewPost({ ...newPost, [name]: value });
};
const handleFileChange = event => {
setNewPost({
...newPost,
file: event.target.files[0]
});
};
const handlePostSubmit = async () => {
try {
const { title, content, file } = newPost;
if (!title || !content) {
setError('Title and content are required fields.');
return;
}
const postData = { title, content };
const response = await axios.post('/api/posts',
postData);
console.log('Post created:', response.data);
setNewPost({ title: '', content: '', file: null });
setError('');
} catch (error) {
console.error('Error creating post:', error);
setError('Error creating post. Please try again.');
}
};
return (
<div className="create-post">
<h2>Create a Post</h2>
{error && <p style={{ color: 'red' }}>{error}</p>}
<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"
accept="image/*" onChange={handleFileChange} />
<button onClick={handlePostSubmit}>
Post
</button>
</div>
);
}
export default CreatePost;
//pages/api/posts.js
import mongoose from 'mongoose';
// MongoDB connection URI
const MONGODB_URI = 'Your Mongo Database URL Here';
// Connect to MongoDB
mongoose.connect(MONGODB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true
});
// Define post schema
const postSchema = new mongoose.Schema({
title: String,
content: String,
file: String,
likes: { type: Number, default: 0 },
comments: [{ text: String }]
});
// Define Post model
const Post = mongoose.models.Post || mongoose.model('Post', postSchema);
// Export API handler
export default async function handler(req, res) {
if (req.method === 'GET') {
try {
const posts = await Post.find();
res.status(200).json(posts);
} catch (error) {
res.status(500).json({
error: 'Internal Server Error'
});
}
} else if (req.method === 'POST') {
try {
const { title, content, file } = req.body;
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'
});
}
} else if (req.method === 'PUT') {
try {
const { id, action } = req.body;
if (!id || !action) {
return res.status(400).json({
error: 'Post ID and action are required'
});
}
let updatedPost;
if (action === 'like') {
updatedPost = await Post.findByIdAndUpdate(id,
{ $inc: { likes: 1 } }, { new: true });
} else if (action === 'comment') {
const { text } = req.body;
if (!text) {
return res.status(400).json({
error: 'Comment text is required'
});
}
updatedPost = await Post.findByIdAndUpdate(id,
{ $push: { comments: { text } } }, { new: true });
} else {
return res.status(400).json({
error: 'Invalid action'
});
}
res.status(200).json(updatedPost);
} catch (error) {
console.error('Error updating post:', error);
res.status(500).json({
error: 'Internal Server Error'
});
}
} else {
res.status(405).end(); // Method Not Allowed
}
}
// components/Post.js
import React, { useState } from 'react';
import axios from 'axios';
function Post({ post }) {
const [commentInput, setCommentInput] = useState('');
const handleLike = postId => {
axios.post(`/api/posts/like/${postId}`)
.then(response => {
// Handle updated post data
})
.catch(
error => console.error('Error liking post:', error));
};
const handleAddComment = (postId, commentText) => {
axios.post(`/api/posts/comment/${postId}`,
{ text: commentText })
.then(response => {
// Handle updated post data
})
.catch(
error => console.error('Error adding comment:', error));
};
return (
<div className="post">
<h3>{post.title}</h3>
<p>{post.content}</p>
{post.file && (
<div>
<img src={`/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>
);
}
export default Post;
Start your application using the following command.
npm run dev
Output:
Database records: