News Media Platform with MERN Stack
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.
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:
- Create a directory named model inside root directory.
- Create a javascript file named articleSchema.js in the model directory for collection news schema.
- Then create another route directory inside root(Backend folder).
- Create another javascript file named articleRoute.js to handle API request.
For Frontend:
- Create a components directory inside root directory(Frontend folder).
- 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:
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:
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 : 100 vh; background : #fff ; background-image : linear-gradient( white , green ); } h 1 { text-align : center ; margin-top : 20px ; } h 2 { text-align : center ; margin-top : 20px ; } .container { display : flex; flex-wrap: wrap; justify- content : center ; max-width : 1200px ; margin-block: 2 rem; gap: 2 rem; } .card { display : flex; flex- direction : column; width : clamp( 20 rem, calc( 20 rem + 2 vw), 22 rem); overflow : hidden ; box-shadow: 0 . 1 rem 1 rem rgba( 0 , 0 , 0 , 0.1 ); border-radius: 1em ; background : #ECE9E6 ; background : linear-gradient(to right , #FFFFFF , #ECE9E6 ); } .card__body { padding : 1 rem; display : flex; flex- direction : column; gap: . 5 rem; } .tag { align-self: flex-start; padding : . 25em . 75em ; border-radius: 1em ; font-size : . 75 rem; } .tag- green { background : green ; color : #fafafa ; } .card__body h 4 { font-size : 1.5 rem; text-transform : capitalize ; } .card__footer { display : flex; padding : 1 rem; margin-top : auto ; } .user { display : flex; gap: . 2 rem; } .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 h 1 { 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
- Output of data saved in Database: