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