Blogging Platform using MERN Stack

The blogging platform is developed using the MERN (MongoDB, ExpressJS, ReactJS, NodeJS) stack, allowing users to create, read, update, and delete blog posts. It provides a user-friendly interface for managing blog content, including features like adding new posts and viewing existing posts.

Output Preview: Let us have a look at how the final output will look like.

Prerequisites:

Approach to Create Blogging Platfrom using MERN:

The project follows a client-server architecture, with the frontend developed using React.js and Material-UI components, and the backend implemented using Express.js and MongoDB for data storage.

  • Fetching existing blog posts from the backend and displaying them on the frontend.
  • Adding new blog posts through a form with title and content fields.
  • Deleting blog posts by clicking on a delete button associated with each post.

Steps to Create the Backend Server:

Step 1: Create a directory for the project.

mkdir server
cd server

Step 2: Initialized the Express app and installing the required packages

npm init -y

Step 3: Install the necessary package in your server using the following command.

npm install express cors

Project Structure(Backend):

The updated dependencies in package.json file of backend will look like:

"dependencies": {
"cors": "^2.8.5",
"express": "^4.18.3",
"mongoose": "^8.2.1"
}
Javascript
const express = require('express');
const mongoose = require('mongoose');
const cors = require('cors');
const bodyParser = require('body-parser');

const app = express();
const PORT = process.env.PORT || 3000;

// MongoDB Connection
mongoose.connect('mongodb://localhost:27017/blogDB',
    {
        useNewUrlParser: true,
        useUnifiedTopology: true
    });
const db = mongoose.connection;
db.on('error', console.error.bind(console,
    'MongoDB connection error:'));
db.once('open', () => {
    console.log('Connected to MongoDB');
});

app.use(cors());
// Post Model
const Post = mongoose.model('Post', {
    title: String,
    content: String,
    createdAt: {
        type: Date,
        default: Date.now
    },
    updatedAt: { type: Date }
});

// Middleware
app.use(bodyParser.json());

// Routes
app.get('/posts', async (req, res) => {
    try {
        const posts = await Post.find();
        res.json(posts);
    } catch (err) {
        res.status(500).json({
            message: err.message
        });
    }
});

app.post('/posts', async (req, res) => {
    const post = new Post({
        title: req.body.title,
        content: req.body.content
    });

    try {
        const newPost = await post.save();
        res.status(201).json(newPost);
    } catch (err) {
        res.status(400).json({ message: err.message });
    }
});

app.put('/posts/:id', async (req, res) => {
    try {
        const updatedPost = await Post.findByIdAndUpdate(
            req.params.id, {
            title: req.body.title,
            content: req.body.content,
            updatedAt: Date.now()
        }, { new: true });
        res.json(updatedPost);
    } catch (err) {
        res.status(400).json({
            message: err.message
        });
    }
});

app.delete('/posts/:id', async (req, res) => {
    try {
        await Post.findByIdAndDelete(req.params.id);
        res.json({ message: 'Post deleted' });
    } catch (err) {
        res.status(500).json({
            message: err.message
        });
    }
});

// Start Server
app.listen(PORT, () => {
    console.log(`Server is running on port ${PORT}`);
});

Start your server using the following command.

node server.js

Steps to Setup Frontend with React:

Step 1: Create React App

npx create-react-app client

Step 2: Switch to the project directory

cd client

Step 3: Installing the required packages:

npm install @material-ui/icons @material-ui/core axios

Step 5: Implement components for displaying blog posts and adding new posts.Use Axios to communicate with the backend API endpoints.

Project Structure(Frontend):

The updated Dependencies in package.json file of frontend will look like:

"dependencies": {
"@material-ui/core": "^4.12.4",
"@material-ui/icons": "^4.11.3",
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"axios": "^1.6.7",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
}
CSS
/* App.css */
body{
  background-color: #efecec;
}
.app {
  font-family: Arial, sans-serif;
  background-color: #efecec;
}

.app-bar {
  background-color: #62929a;
}

.container {
  padding-top: 20px;
}

.card {
  height: 100%;
  padding: 2%;
  display: flex;
  flex-direction: column;
  margin-bottom: 20px;
  background-color: #fff;
  border-radius: 8px;
  box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1);
}

.card-content {
  flex-grow: 1;
  padding: 20px;
}

.card-content input,
.card-content textarea {
  width: 100%;
  margin-bottom: 10px;
  padding: 10px;
  border: 1px solid #ccc;
  border-radius: 4px;
}

.card-content textarea {
  resize: vertical;
  min-height: 100px;
}

.add-post-button {
  padding: 10px;
  background-color: #62929a;
  color: #fff;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  transition: background-color 0.3s;
}

.add-post-button:hover {
  background-color: #507f86;
}

.post-title {
  font-size: 20px;
  font-weight: bold;
  margin-bottom: 10px;
}

.post-content {
  font-size: 16px;
}

.card-actions {
  display: flex;
  justify-content: space-between;
  margin-top: 10px;
}

.card-actions button {
  margin-left: 5px;
}
Javascript
import React, {
    useState,
    useEffect
} from 'react';
import {
    AppBar,
    Toolbar,
    Typography,
    Container,
    Grid,
    Card,
    CardContent,
    TextField,
    Button
} from '@material-ui/core';
import {
    Add as AddIcon
} from '@material-ui/icons';
import axios from 'axios';
import './App.css'; // Import CSS file

const apiUrl = 'http://localhost:3000';

function App() {
    const [posts, setPosts] = useState([]);
    const [newPost, setNewPost] = useState({
        title: '',
        content: ''
    });

    useEffect(() => {
        axios.get(`${apiUrl}/posts`)
            .then(response => {
                setPosts(response.data);
            })
            .catch(error => {
                console.error('Error fetching posts:', error);
            });
    }, []);

    const handleInputChange = (event) => {
        const { name, value } = event.target;
        setNewPost(prevState => ({
            ...prevState,
            [name]: value
        }));
    };

    const handleAddPost = () => {
        axios.post(`${apiUrl}/posts`, newPost)
            .then(response => {
                setPosts(prevState => [...prevState,
                response.data]);
                setNewPost({ title: '', content: '' });
            })
            .catch(error => {
                console.error('Error adding post:', error);
            });
    };

    const handleDeletePost = (id) => {
        axios.delete(`${apiUrl}/posts/${id}`)
            .then(() => {
                setPosts(prevState => prevState.filter(
                    post => post._id !== id));
            })
            .catch(error => {
                console.error('Error deleting post:', error);
            });
    };

    return (
        <div className="app">
            <AppBar position="static" className="app-bar">
                <Toolbar>
                    <Typography variant="h6">
                        My Blog
                    </Typography>
                </Toolbar>
            </AppBar>
            <Container maxWidth="lg" className="container">
                <Grid container spacing={3}>
                    <Grid item xs={12} sm={6} md={4}>
                        <Card className="card">
                            <CardContent className="card-content">
                                <TextField
                                    label="Title"
                                    name="title"
                                    value={newPost.title}
                                    onChange={handleInputChange}
                                    fullWidth
                                    margin="normal"
                                />
                                <TextField
                                    label="Content"
                                    name="content"
                                    value={newPost.content}
                                    onChange={handleInputChange}
                                    multiline
                                    fullWidth
                                    margin="normal"
                                />
                            </CardContent>
                            <Button
                                variant="contained"
                                color="primary"
                                startIcon={<AddIcon />}
                                className="add-post-button"
                                onClick={handleAddPost}
                            >
                                Add Post
                            </Button>
                        </Card>
                    </Grid>
                    {posts.map(post => (
                        <Grid key={post._id} item xs={12} sm={6} md={4}>
                            <Card className="card">
                                <CardContent className="card-content">
                                    <Typography variant="h5"
                                        className="post-title">
                                        {post.title}
                                    </Typography>
                                    <Typography variant="body2"
                                        className="post-content">
                                        {post.content}
                                    </Typography>
                                </CardContent>
                                <div className="card-actions">
                                    <Button
                                        variant="outlined"
                                        color="primary"
                                        onClick={
                                            () => handleDeletePost(post._id)}
                                    >
                                        Delete
                                    </Button>
                                    {/* Update button can be added similarly */}
                                </div>
                            </Card>
                        </Grid>
                    ))}
                </Grid>
            </Container>
        </div>
    );
}

export default App;

Start your application using the following command.

npm start

Output :

This project provides a comprehensive solution for building a blogging platform using the MERN stack. It offers basic and advanced functionalities for managing blog content efficiently. Whether you’re a beginner or an experienced developer, this project serves as a valuable resource for learning and implementing full-stack web development concepts.