Fruit and Vegetable Market Shop using MERN
In this comprehensive guide, we’ll walk through the step-by-step process of creating a Fruit and Vegetable Market Shop using the MERN (MongoDB, Express.js, React, Node.js) stack. This project will showcase how to set up a full-stack web application where users can view, filter, and purchase various fruits and vegetables.
Preview of final output: Let us have a look at how the final output will look like.
Prerequisites:
Approach to create Fruit and Vegetable Market Shop in MERN:
- Import Statements:
- Import necessary dependencies and components.
- React is imported for defining React components.
ProductList
andHeader
are custom components, assumed to be present in the./components
directory.CustomItemContext
is imported, presumably a custom context provider.
- Functional Component:
- Define a functional component named
App
.
- Define a functional component named
- Context Provider:
- Wrap the
Header
andProductList
components inside theCustomItemContext
provider. This suggests that the components within this provider have access to the context provided byCustomItemContext
.
- Wrap the
- Component Rendering:
- Render the following components:
CustomItemContext
: Presumably, this is a context provider that wraps its child components (Header
andProductList
). The purpose of this context is not clear from the provided code snippet.Header
component.ProductList
component.
- Render the following components:
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
Project Structure:
The updated dependencies in package.json file for backend will look like:
"dependencies": {
"cors": "^2.8.5",
"express": "^4.18.2",
"mongoose": "^8.0.0",
}
Example: Create `server.js` and paste the below code.
Javascript
// server.js const express = require( 'express' ); const mongoose = require( 'mongoose' ); const app = express(); const PORT = process.env.PORT || 5000; const cors = require( 'cors' ); mongoose.connect( 'mongodb://localhost/fruitvegmarke' , { useNewUrlParser: true , useUnifiedTopology: true } ); app.use(express.json()); app.use(cors()); // Use the cors middleware const productSchema = new mongoose.Schema({ name: String, type: String, description: String, price: Number, image: String, }); const Product = mongoose.model( 'Product' , productSchema); // Function to seed initial data into the database const seedDatabase = async () => { try { await Product.deleteMany(); // Clear existing data const products = [ { name: 'Apple' , type: 'Fruit' , description: 'Fresh and crispy' , price: 150, image: 'https://media.w3wiki.net/wp-content/uploads/20240104142542/apple.jpg' }, { name: 'Banana' , type: 'Fruit' , description: 'Rich in potassium' , price: 75, image: 'https://media.w3wiki.net/wp-content/uploads/20240104142554/banana.jpg' }, { name: 'Orange' , type: 'Fruit' , description: 'Packed with vitamin C' , price: 200, image: 'https://media.w3wiki.net/wp-content/uploads/20240104142641/orange.jpg' }, { name: 'Carrot' , type: 'Vegetable' , description: 'Healthy and crunchy' , price: 100, image: 'https://media.w3wiki.net/wp-content/uploads/20240104142613/carrot.jpg' }, { name: 'Broccoli' , type: 'Vegetable' , description: 'Nutrient-rich greens' , price: 175, image: 'https://media.w3wiki.net/wp-content/uploads/20240104142601/brocoli.jpg' }, { name: 'Grapes' , type: 'Fruit' , description: 'Sweet and juicy' , price: 250, image: 'https://media.w3wiki.net/wp-content/uploads/20240104142629/grapes.jpg' }, { name: 'Strawberry' , type: 'Fruit' , description: 'Delicious red berries' , price: 300, image: 'https://media.w3wiki.net/wp-content/uploads/20240104142657/strawberry.jpg' }, { name: 'Lettuce' , type: 'Vegetable' , description: 'Crisp and fresh' , price: 120, image: 'https://media.w3wiki.net/wp-content/uploads/20240104142635/lettue.jpg' }, { name: 'Tomato' , type: 'Vegetable' , description: 'Versatile and flavorful' , price: 180, image: 'https://media.w3wiki.net/wp-content/uploads/20240104142704/tomato.jpg' }, { name: 'Cucumber' , type: 'Vegetable' , description: 'Cool and hydrating' , price: 130, image: 'https://media.w3wiki.net/wp-content/uploads/20240104142621/cocumber.jpg' }, ]; await Product.insertMany(products); console.log( 'Database seeded successfully' ); } catch (error) { console.error( 'Error seeding database:' , error); } }; // Seed the database on server startup seedDatabase(); // Define API endpoint for fetching all products app.get( '/api/products' , async (req, res) => { try { // Fetch all products from the database const allProducts = await Product.find(); // Send the entire products array as JSON response res.json(allProducts); } catch (error) { console.error(error); res.status(500) .json({ error: 'Internal Server Error' }); } }); app.listen(PORT, () => { console.log( `Server is running on port ${PORT}` ); }); |
Start the backend server:
node server.js
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
Project Structure:
The updated dependencies in package.json for frontend will look like:
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
}
Javascript
// client/src/App.js import React from 'react' ; import ProductList from './components/ProductList' ; import Header from './components/Header' ; import './App.css' import CustomItemContext from './context/ItemContext' ; const App = () => { return ( <CustomItemContext> <Header /> <ProductList /> </CustomItemContext> ); }; export default App; |
Javascript
//context/ItemContext.js import { createContext, useEffect, useState } from 'react' ; const itemContext = createContext(); // creating custom provider function CustomItemContext({ children }) { const [products, setProducts] = useState([]); const [cart, setCart] = useState([]); const [itemsInCart, setItemsInCart] = useState(0); const [totalPrice, setTotalPrice] = useState(0) // useEffect to load all the vegetables useEffect(() => { // Fetch products from the backend and dispatch 'SET_PRODUCTS' action const fetchData = async () => { const response = await fetch( 'http://localhost:5000/api/products' ); const products = await response.json(); // console.log(products) setProducts(products); }; fetchData(); }, []); const addToCart = (product) => { setTotalPrice(totalPrice + product.price) setCart([...cart, product]); setItemsInCart(itemsInCart + 1); }; const removeFromCart = (product) => { const index = cart.findIndex( (prdt) => prdt._id === product._id); console.log(index); if (index !== -1) { // Item found in the cart // Now you can remove it from the cart array const updatedCart = [...cart]; updatedCart.splice(index, 1); setTotalPrice(totalPrice - cart[index].price); setCart(updatedCart); setItemsInCart(itemsInCart - 1); } else { console.log( "Item not found in the cart" ); } }; return ( // default provider <itemContext.Provider value={ { products, addToCart, removeFromCart, itemsInCart, totalPrice }}> {children} </itemContext.Provider> ); } export { itemContext }; export default CustomItemContext; |
Javascript
// client/src/components/Header.js import React, { useContext } from 'react' ; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faCartShopping } from '@fortawesome/free-solid-svg-icons' import { itemContext } from '../context/ItemContext' ; const Header = () => { const { itemsInCart, totalPrice } = useContext(itemContext) return ( <div className= 'header' > <h1 className= 'gfg' > GFG Fruit & Vegetable Market </h1> <h3 style={{ color: "green" }}> Total Price: {totalPrice} </h3> <div className= 'cart-num' > <div className= 'cart-items' > {itemsInCart} </div> <FontAwesomeIcon icon={faCartShopping} size= "4x" /> </div> </div> ); }; export default Header; |
Javascript
// client/src/components/ProductItem.js import React, { useContext } from 'react' ; import { itemContext } from '../context/ItemContext' ; const ProductItem = ({ product }) => { const { addToCart, removeFromCart } = useContext(itemContext) const handleAddToCart = (product) => { console.log(product) addToCart(product) }; const handleRemoveToCart = (product) => { console.log( "product removed" , product) removeFromCart(product) }; return ( <div className= "product-card" > <img className= "product-image" src={product.image} alt={product.name} /> <div className= "product-details" > <h3 style={{ fontWeight: "700" }}> {product.name} </h3> <p style={{ fontWeight: "300" }}> {product.description} </p> <p style={{ fontWeight: "500" }}> Price: {product.price} Rs/Kg </p> <button onClick={ () => handleAddToCart(product) }> Add to Cart </button> <button onClick={ () => handleRemoveToCart(product) }> - </button> </div> </div> ); }; export default ProductItem; |
Javascript
// client/src/components/ProductList.js import React, { useContext, useEffect, useState } from 'react' ; import ProductItem from './ProductItem' ; import { itemContext } from '../context/ItemContext' ; const ProductList = () => { const { products } = useContext(itemContext); // Keep a local state for sorted products const [sortedProducts, setSortedProducts] = useState([...products]); const [minPrice, setMinPrice] = useState(0); const [maxPrice, setMaxPrice] = useState(3000); // 'all' represents no type filter const [selectedType, setSelectedType] = useState( 'all' ); useEffect(() => { setSortedProducts([...products]) }, [products]) const handleSortByPrice = () => { const sorted = [...sortedProducts] .sort((a, b) => a.price - b.price); setSortedProducts(sorted); }; const handleFilterByPriceRange = () => { const filtered = products.filter( (product) => product.price >= minPrice && product.price <= maxPrice); setSortedProducts(filtered); }; const handleFilterByType = () => { if (selectedType === 'all' ) { // Reset the type filter setSortedProducts([...products]); } else { const filtered = products.filter( (product) => product.type === selectedType); setSortedProducts(filtered); } }; return ( <div className= 'prdt-list' > <h2>Product List</h2> <div className= 'filter-btn' > <button onClick={handleSortByPrice}> Sort by Price </button> <label> Min Price: <input type= 'number' value={minPrice} onChange={ (e) => setMinPrice(Number(e.target.value)) } /> </label> <label> Max Price: <input type= 'number' value={maxPrice} onChange={ (e) => setMaxPrice(Number(e.target.value)) } /> </label> <button onClick={() => handleFilterByPriceRange()}> Filter by Price Range </button> <label> Filter by Type: <select value={selectedType} onChange={ (e) => setSelectedType(e.target.value) }> <option value= 'all' > All </option> <option value= 'Fruit' >Fruit</option> <option value= 'Vegetable' >Vegetable</option> </select> </label> <button onClick={handleFilterByType}> Filter by Type </button> </div> <ul className= 'item-card' > {sortedProducts.map((product) => ( <ProductItem key={product._id} product={product} /> ))} </ul> <div className= 'buy-now-btn' >Buy Now</div> </div> ); }; export default ProductList; |
CSS
/*App.css*/ .cart-items { border-radius: 50% ; background-color : rgb ( 20 , 158 , 105 ); font-weight : 700 ; color : aliceblue; width : 30px ; height : 30px ; font-size : 30px ; padding : 10px ; top : 10px ; position : relative ; left : 30px ; } .header { display : flex; justify- content : space-evenly; align-items: center ; padding : 10px ; border-bottom : 1px sold #ccc ; } /* card */ /* client/src/components/ProductItem.css */ .product-card { border : 1px solid #ddd ; border-radius: 8px ; width : fit-content; padding : 16px ; margin : 16px ; box-shadow: 0 4px 8px rgba( 0 , 0 , 0 , 0.1 ); background-color : #fff ; display : flex; flex- direction : column; align-items: center ; } .product-image { width : 200px ; height : 200px ; object-fit: cover; border-radius: 10px ; margin-bottom : 12px ; transition: transform 0.3 s ease-in-out; } .product-image:hover { transform: scale( 1.1 ); /* Enlarge the image on hover */ } .product-details { text-align : center ; } .item-card { display : flex; flex-wrap: wrap; } h 2 { text-align : center ; } .filter-btn { display : flex; flex- direction : row; padding : 10px ; gap: 10px ; justify- content : center ; } .prdt-list { display : flex; flex- direction : column; justify- content : center ; } .cart-num { margin-bottom : 40px ; cursor : pointer ; } .buy-now-btn { background-color : rgb ( 11 , 162 , 11 ); color : white ; padding : 5px 10px ; border-radius: 10px ; font-size : 2 rem; position : fixed ; top : 30% ; right : 10px ; cursor : pointer ; } .buy-now-btn:hover { background-color : rgb ( 113 , 230 , 113 ); color : brown; } .gfg { background-color : green ; color : white ; padding : 5px 10px ; border-radius: 10px ; } |
To start frontend code:
npm start
Output:
Output of Data Saved in Database: