Product Review Platform using MERN
In this article, we’ll walk through creating a Product Review Platform using the MERN stack (MongoDB, Express.js, React, and Node). By the end of this guide, you’ll have a functional application where users can add products, leave reviews, and delete products.
Preview of final output: Let us have a look at how the final output will look like.
Prerequisites:
Approach to create Product Review Platform:
- State and Components:
- Uses
useState
forproducts
andnewProduct
. - Renders product cards, reviews, and forms.
- Uses
- API Interaction:
- Fetches products on mount.
- Utilizes Axios for CRUD operations.
- Product Operations:
- Adds new products.
- Deletes products.
- Submits and displays reviews.
- User Interface:
- Forms for input.
- Dynamically updates UI.
Steps to Create the Frontend:
Step 1: Set up React frontend using the command.
npx create-react-app client
Step 2: Navigate to the project folder using the command.
cd client
Step 3: Install axios
npm i axios
Project Structure:
The updated dependencies in package.json for frontend will look like:
"dependencies": {
"axios": "^1.5.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
}
Example: Below is the client code.
Javascript
import React, { useState, useEffect } from "react" ; import axios from "axios" ; import "./App.css" ; const App = () => { const [products, setProducts] = useState([]); const [newProduct, setNewProduct] = useState({ name: "" , description: "" , image: "" , }); useEffect(() => { // Fetch products from the server axios .get( "http://localhost:5000/api/products" ) .then((response) => setProducts(response.data)) . catch ((error) => console.error(error)); }, []); const handleReviewSubmit = (productId, review) => { // Submit a new review for a product axios .post( `http: //localhost:5000/api/products/${productId}/review`, review) .then((response) => { // Update the products in the state with the new review const updatedProducts = products.map((product) => product._id === productId ? response.data : product ); setProducts(updatedProducts); }) . catch ((error) => console.error(error)); }; const handleAddProduct = () => { // Submit a new product console.log( "i am called" ); axios .post( "http://localhost:5000/api/products" , newProduct) .then((response) => { // Update the products in the state with the new product setProducts([...products, response.data]); // Clear the newProduct state for the next entry setNewProduct({ name: "" , description: "" , image: "" }); }) . catch ((error) => console.error(error)); }; const handleProductDelete = (productId) => { // Send a DELETE request to the server axios . delete (`http: //localhost:5000/api/products/${productId}`) .then((response) => { // Update the products in the state after successful deletion console.log( "The Producted deleted successfully was:" , response.data.deletedProduct ); const updatedProducts = products.filter( (product) => product._id !== productId ); setProducts(updatedProducts); }) . catch ((error) => console.error( `Error deleting product with ID ${productId}:`, error) ); }; return ( <div className= "outer-cont" > <h1 style={ { marginTop: "10px" , color: "white" }}> GFG </h1> <h2>Product Review Platform</h2> <div className= "add-container" > <h3>Add a New Product:</h3> <form onSubmit={(e) => { e.preventDefault(); handleAddProduct(); }}> <label> Name:{ " " } <input type= "text" name= "name" value={newProduct.name} onChange={(e) => setNewProduct( { ...newProduct, name: e.target.value } ) } required /> </label> <label> Description:{ " " } <input type= "text" name= "description" value={newProduct.description} onChange={(e) => setNewProduct( { ...newProduct, description: e.target.value } ) } required /> </label> <label> { " " } Image URL:{ " " } <input type= "text" name= "image" value={newProduct.image} onChange={(e) => setNewProduct( { ...newProduct, image: e.target.value }) } required /> </label> <button className= "add-btn" type= "submit" > Add Product </button> </form> </div> <div className= "cards" > {products.map((product) => ( <div key={product._id} className= "product-card" > <h2>{product.name}</h2> <button className= "delete-btn" onClick={ () => handleProductDelete(product._id) }> Delete Product </button> <img src={product.image} style={{ width: "300px" }} alt= "" /> <p>{product.description}</p> <h3>Reviews:</h3> <ul> {product.reviews.map((review, index) => ( <li key={index}> <strong>{review.user}</strong> - {review.rating}/5:{ " " } {review.comment} </li> ))} </ul> <h3>Add a Review:</h3> <form onSubmit={(e) => { e.preventDefault(); const user = e.target.elements.user.value; const rating = e.target.elements.rating.value; const comment = e.target.elements.comment.value; handleReviewSubmit(product._id, { user, rating, comment }); }}> <label> User: <input type= "text" name= "user" required /> </label> <label> Rating:{ " " } <input type= "number" name= "rating" min= "1" max= "5" required /> </label> <label> Comment: <textarea name= "comment" required> </textarea> </label> <button type= "submit" > Submit Review </button> </form> </div> ))} </div> </div> ); }; export default App; |
CSS
body { font-family : 'Arial' , sans-serif ; margin : 0 ; padding : 0 ; } .app-container { text-align : center ; } .product-card { border : 1px solid #ddd ; border-radius: 11px ; box-shadow: 0 8px 35px rgba( 0 , 0 , 0 , 0.1 ); padding : 10px ; margin : 11px ; width : 300px ; } .product-card h 2 { margin-bottom : 10px ; color : #333 ; } .product-card p { color : #666 ; } .product-card h 3 { margin : 10px 0 ; color : #333 ; } .product-card ul { list-style-type : none ; padding : 0 ; } .product-card ul li { margin-bottom : 2px ; color : #555 ; } .product-card form { margin-top : 10px ; } .product-card label { display : block ; margin-bottom : 5px ; color : #333 ; gap: 10px ; } .product-card input, .product-card textarea { width : 100% ; padding : 3px ; margin-bottom : px; box-sizing: border-box; } label { margin : 10px ; } .product-card button { background-color : #007BFF ; color : #fff ; border : none ; padding : 10px ; cursor : pointer ; border-radius: 4px ; } .product-card button:hover { background-color : #0056b3 ; } @media ( max-width : 600px ) { .product-card { width : 100% ; } } .cards { display : flex; flex-wrap: wrap; justify- content : space-around; max-width : 1200px ; margin : 20px auto ; } .delete-btn { background-color : red ; color : white ; margin-bottom : 15px ; } .add-container { display : flex; flex- direction : column; justify- content : center ; align-items: center ; } h 1 { text-align : center ; background-color : rgb ( 8 , 149 , 8 ); color : white ; width : 70 vw; padding : 5px ; border-radius: 10px ; font-size : larger ; margin : 20px ; } h 2 { text-align : center ; margin-bottom : -5px ; margin-top : -5px ; } h 3 { color : #007BFF ; } .outer-cont { display : flex; flex- direction : column; justify- content : center ; align-items: center ; } .add-btn { background-color : rgb ( 8 , 149 , 8 ); color : #121010 ; margin-left : 5px ; border : none ; padding : 5px 10px ; border-radius: 10px ; } img { width : 200px ; height : 200px ; } |
Step to Run Your React App
npm start
Your React app should now be running at http://localhost:3000. It displays a form for adding new products and a list of existing products with their reviews.
Steps to Create the Backend:
Step 1: Create a directory for project
npm init backend
Step 2: Open project using the command
cd backend
Step 3: Installing the required packages
npm install express mongoose cors body-parser
Project Structure:
The updated dependencies in package.json file for backend will look like:
"dependencies": {
"body-parser": "^1.20.2",
"cors": "^2.8.5",
"express": "^4.18.2",
"mongoose": "^8.0.0",
}
Example: Create a new file `server.js` and write the provided Nodejs code.
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 || 5000; app.use(cors()); app.use(express.json()); // Middleware for JSON parsing app.use(bodyParser.json()); // Connect to MongoDB mongoose.connect( 'mongodb://localhost:27017/product_review' , { useNewUrlParser: true , useUnifiedTopology: true , }).then(() => { console.log( 'Connected to MongoDB' ) }); // Define the Product schema const productSchema = new mongoose.Schema({ name: String, description: String, image: String, reviews: [ { user: String, rating: Number, comment: String, }, ], }); const Product = mongoose.model( 'Product' , productSchema); // API endpoints // Route to add a new product app.post( '/api/products' , async (req, res) => { try { const { name, description, image } = req.body; // Validate request data if (!name || !description || !image) { return res.status(400).json( { message: 'Incomplete product data' } ); } // Create a new product const newProduct = new Product({ name, description, image, reviews: [], }); // Save the new product to the database const savedProduct = await newProduct.save(); // Respond with the newly added product res.status(201).json(savedProduct); } catch (error) { console.error( 'Error adding product:' , error); res.status(500) .json( { message: 'Internal Server Error' } ); } }); app.get( '/api/products' , async (req, res) => { try { const products = await Product.find(); res.json(products); } catch (error) { res.status(500).json({ message: error.message }); } }); app.post( '/api/products/:id/review' , async (req, res) => { const { user, rating, comment } = req.body; try { const product = await Product.findById(req.params.id); product.reviews .push( { user, rating, comment } ); await product.save(); res.status(201).json(product); } catch (error) { res.status(400).json({ message: error.message }); } }); // Delete a product by ID app. delete ( '/api/products/:id' , async (req, res) => { const productId = req.params.id; try { // Find the product by ID and delete it from the database const deletedProduct = await Product.findByIdAndDelete(productId); if (!deletedProduct) { return res.status(404) .json( { message: 'Product not found' } ); } res.json( { message: 'Product deleted' , deletedProduct } ); } catch (error) { console.error( 'Error deleting product:' , error); res.status(500) .json( { message: 'Internal Server Error' } ); } }); app.listen(PORT, () => { console.log(`Server is running on port ${PORT}`); }); |
Steps to run the server:
node server.js
Output: Your server should now be running at http://localhost:5000
Output: Data saved in Database
Contact Us