Open In App

Product Review Platform using MERN

Last Updated : 05 Jan, 2024
Improve
Improve
Like Article
Like
Save
Share
Report

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.

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


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

postNode1GIF

Output

Output: Data saved in Database

hoodiedb

Db



Like Article
Suggest improvement
Previous
Next
Share your thoughts in the comments

Similar Reads