Introduction
As a MERN stack developer, one of the best ways to hone your skills and build a comprehensive portfolio is to create real-world applications. In this article, we will build a simple yet functional blogging platform using the MERN stack (MongoDB, Express, React, Node.js). This project will cover user authentication, CRUD operations for blog posts, and basic responsive UI design.
Project Overview
Our blogging platform will allow users to:
Register and log in
Create, read, update, and delete blog posts
Comment on posts
Navigate through a responsive user interface
Technology Stack
Frontend: React
Backend: Node.js and Express
Database: MongoDB
Authentication: JWT (JSON Web Tokens)
Setting Up the Project
1. Backend Setup
We start by setting up the backend, which will handle user authentication, post creation, and data management.
Initialize the Backend
Create and Navigate to the Backend Directory:
mkdir blog-platform cd blog-platform mkdir backend cd backend npm init -y npm install express mongoose dotenv jsonwebtoken bcryptjs
Create the Project Structure:
backend/ ├── models/ ├── routes/ ├── controllers/ ├── middleware/ ├── .env ├── server.js
Set Up the Server (
server.js
):
const express = require('express');
const mongoose = require('mongoose');
const dotenv = require('dotenv');
dotenv.config();
const app = express();
app.use(express.json());
const connectDB = async () => {
let connected = false;
while (!connected) {
try {
await mongoose.connect(process.env.MONGO_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
console.log('Connected to MongoDB');
connected = true;
} catch (err) {
console.error(err.message);
console.log('Retrying MongoDB connection in 5 seconds...');
await new Promise(res => setTimeout(res, 5000)); // Wait 5 seconds before retrying
}
}
};
connectDB();
const server = app.listen(process.env.PORT || 5000, () =>
console.log(`Server running on port ${process.env.PORT || 5000}`)
);
// Graceful shutdown
process.on('SIGTERM', () => {
console.info('SIGTERM signal received.');
console.log('Closing http server.');
server.close(() => {
console.log('Http server closed.');
mongoose.connection.close(false, () => {
console.log('MongoDb connection closed.');
process.exit(0);
});
});
});
Configure Environment Variables (
.env
):MONGO_URI=your_mongo_connection_string JWT_SECRET=your_jwt_secret
Create Models
User Model (
models/User.js
):const mongoose = require('mongoose'); const UserSchema = new mongoose.Schema({ username: { type: String, required: true, unique: true }, email: { type: String, required: true, unique: true }, password: { type: String, required: true } }); module.exports = mongoose.model('User', UserSchema);
Post Model (
models/Post.js
):const mongoose = require('mongoose'); const PostSchema = new mongoose.Schema({ title: { type: String, required: true }, content: { type: String, required: true }, author: { type: mongoose.Schema.Types.ObjectId, ref: 'User' }, createdAt: { type: Date, default: Date.now } }); module.exports = mongoose.model('Post', PostSchema);
Create Controllers
User Controller (
controllers/userController.js
):const User = require('../models/User'); const bcrypt = require('bcryptjs'); const jwt = require('jsonwebtoken'); exports.register = async (req, res) => { const { username, email, password } = req.body; try { const hashedPassword = await bcrypt.hash(password, 10); const newUser = new User({ username, email, password: hashedPassword }); await newUser.save(); res.status(201).json(newUser); } catch (error) { res.status(400).json({ message: error.message }); } }; exports.login = async (req, res) => { const { email, password } = req.body; try { const user = await User.findOne({ email }); if (!user) return res.status(400).json({ message: "User not found" }); const isMatch = await bcrypt.compare(password, user.password); if (!isMatch) return res.status(400).json({ message: "Invalid credentials" }); const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET, { expiresIn: '1h' }); res.json({ token, user: { id: user._id, username: user.username, email: user.email } }); } catch (error) { res.status(400).json({ message: error.message }); } };
Create Routes
User Routes (
routes/userRoutes.js
):const router = require('express').Router(); const { register, login } = require('../controllers/userController'); router.post('/register', register); router.post('/login', login); module.exports = router;
Post Routes (
routes/postRoutes.js
):const router = require('express').Router(); const Post = require('../models/Post'); router.post('/', async (req, res) => { const { title, content, author } = req.body; try { const newPost = new Post({ title, content, author }); await newPost.save(); res.status(201).json(newPost); } catch (error) { res.status(400).json({ message: error.message }); } }); router.get('/', async (req, res) => { try { const posts = await Post.find().populate('author', 'username'); res.json(posts); } catch (error) { res.status(400).json({ message: error.message }); } }); module.exports = router;
Create Middleware
Auth Middleware (
middleware/auth.js
):const jwt = require('jsonwebtoken'); const auth = (req, res, next) => { const token = req.header('x-auth-token'); if (!token) return res.status(401).json({ message: "No token, authorization denied" }); try { const decoded = jwt.verify(token, process.env.JWT_SECRET); req.user = decoded; next(); } catch (error) { res.status(400).json({ message: "Token is not valid" }); } }; module.exports = auth;
2. Frontend Setup
Initialize the Frontend
Create and Navigate to the Frontend Directory:
npx create-react-app frontend cd frontend npm install axios react-router-dom
Create the Project Structure:
frontend/ ├── public/ ├── src/ ├── components/ ├── pages/ ├── App.js ├── index.js ├── ...
Create Components
Login Component (
components/Login.js
):import React, { useState } from 'react'; import axios from 'axios'; const Login = ({ setAuth }) => { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const handleSubmit = async (e) => { e.preventDefault(); try { const res = await axios.post('/api/auth/login', { email, password }); setAuth(res.data.token); } catch (error) { console.error(error); } }; return ( <form onSubmit={handleSubmit}> <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} /> <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} /> <button type="submit">Login</button> </form> ); }; export default Login;
Register Component (
components/Register.js
):import React, { useState } from 'react'; import axios from 'axios'; const Register = () => { const [username, setUsername] = useState(''); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const handleSubmit = async (e) => { e.preventDefault(); try { await axios.post('/api/auth/register', { username, email, password }); // Handle success, e.g., redirect to login } catch (error) { console.error(error); } }; return ( <form onSubmit={handleSubmit}> <input type="text" value={username} onChange={(e) => setUsername(e.target.value)} /> <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} /> <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} /> <button type="submit">Register</button> </form> ); }; export default Register;
Create Pages
Home Page (
pages/Home.js
):import React, { useEffect, useState } from 'react'; import axios from 'axios'; const Home = () => { const [posts, setPosts] = useState([]); useEffect(() => { const fetchPosts = async () => { try { const res = await axios.get('/api/posts'); setPosts(res.data); } catch (error) { console.error(error); } }; fetchPosts(); }, []); return ( <div> {posts.map(post => ( <div key={post._id}> <h2>{post.title}</h2> <p>{post.content}</p> <small>By: {post.author.username}</small> </div> ))} </div> ); }; export default Home;
Advanced Features
Once you have the basic blogging platform running, you can consider adding the following advanced features to enhance functionality and user experience:
Commenting System:
Allow users to comment on blog posts.
Create a
Comment
model and related API routes.
File Uploads:
Enable users to upload images or files with their posts.
Use a library like
multer
for handling file uploads.
WYSIWYG Editor:
Implement a rich text editor for creating and editing posts.
Integrate libraries like
Draft.js
orQuill
.
User Profiles:
Allow users to create and edit their profiles.
Display user information on the profile page.
Pagination:
Implement pagination for listing posts.
Optimize loading times and improve user experience.
Deployment
Deploying your MERN stack application can be done using various platforms like Heroku, Vercel, or Netlify. Here’s a brief overview of deploying the backend on Heroku and the frontend on Netlify:
Deploying Backend on Heroku
Install Heroku CLI:
npm install -g heroku
Login to Heroku:
heroku login
Create a Heroku App:
heroku create blog-platform-backend
Deploy to Heroku:
git add . git commit -m "Deploy backend" git push heroku main
Set Environment Variables on Heroku:
heroku config:set MONGO_URI=your_mongo_connection_string JWT_SECRET=your_jwt_secret
Deploying Frontend on Netlify
Install Netlify CLI:
npm install -g netlify-cli
Login to Netlify:
netlify login
Build the React App:
npm run build
Deploy to Netlify:
netlify deploy --prod --dir=build
Optimizing Performance
Backend Optimization:
Implement caching strategies using Redis or similar technologies.
Use indexing in MongoDB for faster query performance.
Frontend Optimization:
Lazy load components and images.
Use code-splitting and minification techniques.
Monitoring and Analytics:
Set up monitoring tools like New Relic or Datadog to track application performance.
Use Google Analytics for user behavior tracking.
Conclusion
By following this guide, you will have created a basic blogging platform that includes essential features like user authentication and CRUD operations for blog posts. This project is a great way to demonstrate your MERN stack skills and can be further expanded with additional features like post commenting, file uploads, and enhanced UI/UX design.
Building full-stack applications like this not only strengthens your understanding of each component in the MERN stack but also provides a tangible project that you can showcase in your portfolio. Happy coding!
Feel free to expand this article with additional sections on implementing specific features or more detailed deployment instructions. This comprehensive guide should serve as a solid foundation for building and enhancing your MERN stack projects.