Open In App

News Media Platform with MERN Stack

Last Updated : 07 Mar, 2024
Improve
Improve
Like Article
Like
Save
Share
Report

In this article, we’ll walk through the step-by-step process of creating a News Media Platform using the MERN (MongoDB, ExpressJS, React, NodeJS) stack. This project will showcase how to set up a full-stack web application where users can view a news article, add a new news article, and delete one.

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

Screenshot-2024-03-06-165110

Output

Prerequisites:

Approach to Create a News Media Platform:

  • List all the requirement for the project and make the structure of the project accordingly.
  • Chooses the required dependencies and requirement which are more suitable for the project.

For Backend:

  1. Create a directory named model inside root directory.
  2. Create a javascript file named articleSchema.js in the model directory for collection news schema.
  3. Then create another route directory inside root(Backend folder).
  4. Create another javascript file named articleRoute.js to handle API request.

For Frontend:

  1. Create a components directory inside root directory(Frontend folder).
  2. Create three file of javascript inside components folder namely DeleteArticle.jsx, NewsArticleForm.jsx and NewsList.jsx which are used to delete, add new article and show a list of news respectively.

Steps to Create the Backend:

Step 1: Create a directory for project

mkdir Backend
cd Backend

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

npm init -y
npm i express mongoose cors nodemon

Project Structure:

Backend_PS

Backend project structure

The package.json file of backend will look like:

"dependencies": {
"cors": "^2.8.5",
"express": "^4.18.2",
"mongoose": "^8.2.0",
"nodemon": "^3.1.0"
}

Example : Below is an example of creating a server for New Media platform.

Javascript




const express = require('express');
const mongoose = require('mongoose');
const bodyParser = require('body-parser');
const articleRouter = require('./route/articleRoute.js');
const cors = require('cors')
 
const app = express();
const PORT = process.env.PORT || 5000;
 
// Middleware
app.use(cors())
app.use(bodyParser.json());
 
// Connect to MongoDB
mongoose.connect('mongodb://localhost:27017/newsapp', {
    useNewUrlParser: true,
    useUnifiedTopology: true
})
    .then(() => console.log('MongoDB connected'))
    .catch(err => console.log(err));
 
 
 
app.use('/api/articles', articleRouter);
 
 
app.listen(PORT, () => console.log(`Server started on port ${PORT}`));


Javascript




const mongoose = require('mongoose');
 
const articleSchema = new mongoose.Schema({
    title: {
        type: String,
        required: true
    },
    author: {
        type: String,
        required: true
    },
    content: {
        type: String,
        required: true
    },
    category: {
        type: String,
        required: true
    },
    createdAt: {
        type: Date,
        default: Date.now
    }
});
 
const Article = mongoose.model('Article', articleSchema);
 
module.exports = Article;


Javascript




const express = require('express');
const router = express.Router();
const Article = require('../model/articleSchema.js');
 
// Get all articles
router.get('/', async (req, res) => {
    try {
        const articles = await Article.find();
        res.json(articles);
    } catch (err) {
        res.status(500).json({
            message: err.message
        });
    }
});
 
// Get a specific article
router.get('/:id', async (req, res) => {
    try {
        const article = await Article.findById(req.params.id);
        if (article == null) {
            return res.status(404).json({
                message: 'Article not found'
            });
        }
        res.json(article)
    } catch (err) {
        return res.status(500).json({
            message: err.message
        });
    }
});
 
// Create an article
router.post('/', async (req, res) => {
    try {
        const article = new Article({
            title: req.body.title,
            author: req.body.author,
            content: req.body.content,
            category: req.body.category
        });
 
        const newArticle = await article.save();
        res.status(201).json(newArticle);
    } catch (err) {
        res.status(400).json({
            message: err.message
        });
    }
});
 
// Update an article
router.put('/:id', async (req, res) => {
    try {
        const article = await Article.findByIdAndUpdate(
            req.params.id, req.body, { new: true });
        if (!article) {
            return res.status(404).json({
                message: 'Article not found'
            });
        }
        res.json(article);
    } catch (err) {
        res.status(400).json({ message: err.message });
    }
});
 
// Delete an article
router.delete('/:id', async (req, res) => {
    try {
        const article = await Article.findByIdAndDelete(req.params.id);
        if (!article) {
            return res.status(404).json({
                message: 'Article not found'
            });
        }
        res.json({ message: 'Article deleted' });
    } catch (err) {
        res.status(500).json({
            message: err.message
        });
    }
});
 
module.exports = router;


Start your server using the following command.

node index.js

Steps to Create the Frontend:

Step 1: Initialized the React App with Vite and installing the required packages

npm create vite@latest -y
->Enter Project name: "Frontend"
->Select a framework: "React"
->Select a Variant: "Javascript"
cd Frontend
npm install

Project Structure:

Frontend_PS

Frontend Project structure

The package.json file of frontend will look like:

 "dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@types/react": "^18.2.56",
"@types/react-dom": "^18.2.19",
"@vitejs/plugin-react": "^4.2.1",
"vite": "^5.1.4"
}
}

Example: Below is an example of creating a frontend of News Media platform.

CSS




/* ./src/index.css */
 
*,
*::before,
*::after {
    box-sizing: border-box;
    padding: 0;
    margin: 0;
}
 
body {
    display: grid;
    min-height: 100vh;
    background: #fff;
    background-image: linear-gradient(white, green);
}
 
h1 {
    text-align: center;
    margin-top: 20px;
}
 
h2 {
    text-align: center;
    margin-top: 20px;
}
 
.container {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    max-width: 1200px;
    margin-block: 2rem;
    gap: 2rem;
}
 
.card {
    display: flex;
    flex-direction: column;
    width: clamp(20rem, calc(20rem + 2vw), 22rem);
    overflow: hidden;
    box-shadow: 0 .1rem 1rem rgba(0, 0, 0, 0.1);
    border-radius: 1em;
    background: #ECE9E6;
    background: linear-gradient(to right, #FFFFFF, #ECE9E6);
 
}
 
.card__body {
    padding: 1rem;
    display: flex;
    flex-direction: column;
    gap: .5rem;
}
 
 
.tag {
    align-self: flex-start;
    padding: .25em .75em;
    border-radius: 1em;
    font-size: .75rem;
}
 
 
.tag-green {
    background: green;
    color: #fafafa;
}
 
 
.card__body h4 {
    font-size: 1.5rem;
    text-transform: capitalize;
}
 
.card__footer {
    display: flex;
    padding: 1rem;
    margin-top: auto;
}
 
.user {
    display: flex;
    gap: .2rem;
}
 
.form-container {
    width: 500px;
    margin: 50px auto;
    background-color: white;
    padding: 20px;
    border-radius: 20px;
 
}
 
.form-group {
    margin-bottom: 15px;
 
}
 
.form-group label {
    display: block;
    margin-bottom: 5px;
}
 
.form-group input {
    width: 100%;
    padding: 5px;
}
 
.form-group textarea {
    width: 100%;
    padding: 5px;
}
 
.form-group select {
    width: 100%;
    padding: 5px;
    background-color: #f2f2f2;
    border: none;
    border-radius: 5px;
    appearance: none;
    -webkit-appearance: none;
    -moz-appearance: none;
    cursor: pointer;
}
 
.form-group select::-ms-expand {
    display: none;
}
 
.form-group select:focus {
    outline: none;
    box-shadow: 0 0 5px rgba(0, 0, 0, 0.2);
}
 
.form-group button {
    background-color: #4CAF50;
    color: white;
    padding: 5px 10px;
    border: none;
    cursor: pointer;
    width: 100%;
}
 
.form-group button:hover {
    background-color: #45a049;
}
 
button {
    background-color: #4CAF50;
    color: white;
    padding: 10px 20px;
    border: none;
    border-radius: 5px;
    cursor: pointer;
    display: block;
    margin: 0 auto;
}
 
button:hover {
    background-color: #45a049;
}
 
.around {
    width: 100%;
    display: flex;
    justify-content: space-between;
}
 
.around h1 {
    margin-left: 50px;
}
 
.around button {
    align-self: flex-end;
}
 
#tbl button {
    background-color: #cb2d3e !important;
}
 
#tbl button:hover {
    background-color: #c2424f;
}
 
#tbl-head {
    width: 80%;
    background-color: white;
    padding: 20px;
    margin: 50px auto;
    border-radius: 20px;
}
 
#tbl {
    width: 90%;
    border-collapse: collapse;
    margin: 0 auto;
    margin-bottom: 50px;
    margin-top: 30px;
}
 
#tbl td,
#tbl th {
    border: 1px solid #ddd;
    padding: 8px;
}
 
#tbl th {
    padding-top: 12px;
    padding-bottom: 12px;
    text-align: left;
    color: green;
}


Javascript




// ./src/main.jsx
 
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.jsx'
import './index.css'
 
ReactDOM.createRoot(document.getElementById('root')).render(
    <React.StrictMode>
        <App />
    </React.StrictMode>,
)


Javascript




// src/App.js
 
import React, { useState } from "react";
import NewArticleForm from "./components/NewArticleForm.jsx";
import DeleteArticle from "./components/DeleteArticle.jsx";
import NewsList from "./components/NewsList.jsx";
import "./index.css";
function App() {
    const [isAuther, setAuther] = useState(false);
    return (
        <div>
            <div className="around">
                <h1>GFG News App</h1>
                <button onClick={() => setAuther(!isAuther)}>
                    Switch to {isAuther ? "Viewer" : "Author"}
                </button>
            </div>
            {isAuther ? (
                <>
                    <NewArticleForm setAuther={setAuther} />
                    <DeleteArticle />
                </>
            ) : (
                <NewsList />
            )}
        </div>
    );
}
 
export default App;


Javascript




// ./src/components/DeleteArticle.jsx
 
import { useEffect, useState } from "react";
 
const DeleteArticle = () => {
    const [articles, setArticles] = useState([]);
    const [deleted, setDelete] = useState(true);
 
    useEffect(() => {
        // Fetch articles from backend when component mounts
        fetch("http://localhost:5000/api/articles")
            .then((response) => response.json())
            .then((data) => setArticles(data))
            .catch((error) =>
                console.error("Error fetching articles:", error));
    }, [deleted]);
 
    const handleDelete = async (articleid) => {
        try {
            const response = await fetch(
                `http://localhost:5000/api/articles/${articleid}`,
                {
                    method: "DELETE",
                }
            );
 
            if (!response.ok) {
                throw new Error("Failed to delete article");
            }
 
            alert("Article deleted");
            setDelete(!deleted);
            /*
                Assuming you have a state or effect
                to update the list of articles after deletion
                */
        } catch (error) {
            console.error("Error deleting article:", error);
        }
    };
    return (
        <div id="tbl-head">
            <h1>Articles</h1>
            <table id="tbl">
                <tr>
                    <th>Title</th>
                    <th>Category</th>
                    <th>Author</th>
                    <th>Date</th>
                    <th>Action</th>
                </tr>
                {articles.map((article) => (
                    <tr>
                        <td>{article.title}</td>
                        <td>{article.category}</td>
                        <td>{article.author}</td>
                        <td>{article.createdAt}</td>
                        <td>
                            <button
                                className="dl-btn"
                                onClick={() => handleDelete(article._id)}
                            >
                                Delete
                            </button>
                        </td>
                    </tr>
                ))}
            </table>
        </div>
    );
};
 
export default DeleteArticle;


Javascript




// ./src/components/NewArticleForm.js
 
import React, { useState } from 'react';
 
function NewArticleForm({ setAuther }) {
    const [formData, setFormData] = useState({
        title: '',
        author: '',
        content: '',
        category: ''
    });
 
    const handleChange = event => {
        setFormData({
            ...formData,
            [event.target.name]: event.target.value
        });
    };
 
    const handleSubmit = async event => {
        event.preventDefault();
 
        try {
            const response = await
                fetch('http://localhost:5000/api/articles', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json'
                    },
                    body: JSON.stringify(formData)
                });
 
            if (!response.ok) {
                throw new Error('Failed to create article');
            }
 
            // Reset form data after successful submission
            setFormData({
                title: '',
                author: '',
                content: '',
                category: ''
            });
 
            alert("Article Added..")
            setAuther(false)
 
        } catch (error) {
            console.error('Error creating article:', error);
        }
    };
 
    return (
        <div> {/* Centered container */}
            <div className="form-container">
                <h2>Add New Article</h2>
                <form onSubmit={handleSubmit}>
                    <div className="form-group">
                        <label >
                            Title:
                            <input
                                type="text"
                                name="title"
                                value={formData.title}
                                onChange={handleChange}
                                className="form-input"
                            />
                        </label>
                    </div>
                    <div className="form-group">
                        <label>
                            Author:
                            <input
                                type="text"
                                name="author"
                                value={formData.author}
                                onChange={handleChange}
                                className="form-input"
                            />
                        </label>
                    </div>
                    <div className="form-group">
                        <label>
                            Content:
                            <textarea
                                name="content"
                                value={formData.content}
                                onChange={handleChange}
                                className="form-textarea"
                            />
                        </label>
                    </div>
                    <div className="form-group">
                        <label>
                            Category:
                            <input
                                type="text"
                                name="category"
                                value={formData.category}
                                onChange={handleChange}
                                className="form-input"
                            />
                        </label>
                    </div>
                    <button type="submit"
                        className="submit-button">
                        Add Article
                    </button>
                </form>
            </div>
 
 
        </div>
    );
}
 
export default NewArticleForm;


Javascript




// ./src/components/NewsList.js
 
import React, { useState, useEffect } from 'react';
 
function NewsList() {
    const [articles, setArticles] = useState([]);
 
    useEffect(() => {
        // Fetch articles from backend when component mounts
        fetch('http://localhost:5000/api/articles')
            .then(response => response.json())
            .then(data => setArticles(data))
            .catch(error =>
                console.error('Error fetching articles:', error));
    }, []);
 
    return (
        <div>
            <div className="App">
                <div class="container">
                    {articles.map(article => (
                        <div class="card">
 
                            <div class="card__body">
                                <span class="tag tag-green">
                                    {article.category}
                                </span>
                                <h4>{article.title}</h4>
                                <p>{article.content}</p>
                            </div>
                            <div class="card__footer">
                                <div class="user">
                                    <div class="user__info">
                                        <h5>{article.author}</h5>
                                        <small>{article.createdAt}</small>
                                    </div>
                                </div>
                            </div>
                        </div>
                    ))}
                </div>
            </div>
 
        </div>
    );
}
 
export default NewsList;


Start your frontend application using the following command.

npm run dev

Output:

  • Browser Output
gfg69

Output

  • Output of data saved in Database:
gfg69

Database output



Like Article
Suggest improvement
Share your thoughts in the comments

Similar Reads