Product Review Platform using MERN

Last Updated : 23 Jul, 2025

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.

hoodiepreview
Output Preview

Prerequisites:

Approach to create Product Review Platform:

  • State and Components:
    • Uses useState for products and newProduct.
    • Renders product cards, reviews, and forms.
  • 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:

Screenshot-2566-12-31-at-150640
Client folder 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.

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 h2 {
    margin-bottom: 10px;
    color: #333;
}

.product-card p {
    color: #666;
}

.product-card h3 {
    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;


}

h1 {
    text-align: center;
    background-color: rgb(8, 149, 8);
    color: white;
    width: 70vw;
    padding: 5px;
    border-radius: 10px;
    font-size: larger;
    margin: 20px;
}

h2 {
    text-align: center;
    margin-bottom: -5px;
    margin-top: -5px;
}

h3 {
    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;

}
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;

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:

Screenshot-2566-12-31-at-150800
Server folder 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

hoodiedb
Db
Comment

Explore