Bookstore E-commerce project is a great way to showcase your understanding of full-stack development. In this article, we’ll walk through the step-by-step process of creating a Bookstore E-commerce 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 books.
Preview of final output: Let us have a look at how the final application will look like:
Prerequisites:
- React JS and topics like Context Api.
- MongoDB and Mongoose
- Express
- Node JS
- MERN Stack
Approach to create Bookstore Ecommerce:
- List all the requirement for the project and make the flowand structure of the project accordingly.
- Chooses the required dependencies and requirement which are more suitable for the project.
- ProductList and Header are custom components, assumed to be present in the ./components directory.
- CustomItemContext is imported, presumably a custom context provider.
- Define a functional component named App.
- Wrap the Header and ProductList components inside the CustomItemContext provider. This suggests that the components within this provider have access to the context provided by CustomItemContext.
- CustomItemContext: Presumably, this is a context provider that wraps its child components (Header and ProductList). The purpose of this context is not clear from the provided code snippet.
Steps to Create the Backend:
Step 1: Create a directory for project
mkdir server
cd server
Step 2: Initialized the Express app and installing the required packages
npm init -y
npm i express mongoose cors
Project Structure:
The updated dependencies in package.json file of backend will look like:
"dependencies": {
"cors": "^2.8.5",
"express": "^4.18.2",
"mongoose": "^8.0.0",
}
Example: Create `server.js` and write the below code.
// 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( '<Your connection string>' , { useNewUrlParser: true , useUnifiedTopology: true });
app.use(express.json()); app.use(cors()); // Use the cors middleware
const bookSchema = new mongoose.Schema({
title: String,
author: String,
genre: String,
description: String,
price: Number,
image: String,
}); const Book = mongoose.model( 'Book' , bookSchema);
// Function to seed initial data into the database const seedDatabase = async () => { try {
await Book.deleteMany(); // Clear existing data
const books = [
{ title: 'The Great Gatsby' , author: 'F. Scott Fitzgerald' , genre: 'Fiction' , description: 'A classic novel about the American Dream' , price: 20, image: 'https://media.geeksforgeeks.org/wp-content/uploads/20240110011815/sutterlin-1362879_640-(1).jpg' },
{ title: 'To Kill a Mockingbird' , author: 'Harper Lee' , genre: 'Fiction' , description: 'A powerful story of racial injustice and moral growth' , price: 15, image: 'https://media.geeksforgeeks.org/wp-content/uploads/20240110011854/reading-925589_640.jpg' },
{ title: '1984' , author: 'George Orwell' , genre: 'Dystopian' , description: 'A dystopian vision of a totalitarian future society' , price: 255, image: 'https://media.geeksforgeeks.org/wp-content/uploads/20240110011929/glasses-1052010_640.jpg' },
{ title: 'The Great Gatsby' , author: 'F. Scott Fitzgerald' , genre: 'Fiction' , description: 'A classic novel about the American Dream' , price: 220, image: 'https://media.geeksforgeeks.org/wp-content/uploads/20240110011929/glasses-1052010_640.jpg' },
{ title: 'To Kill a Mockingbird' , author: 'Harper Lee' , genre: 'Fiction' , description: 'A powerful story of racial injustice and moral growth' , price: 1115, image: 'https://media.geeksforgeeks.org/wp-content/uploads/20240110011929/glasses-1052010_640.jpg' },
{ title: '1984' , author: 'George Orwell' , genre: 'Dystopian' , description: 'A dystopian vision of a totalitarian future society' , price: 125, image: 'https://media.geeksforgeeks.org/wp-content/uploads/20240110011929/glasses-1052010_640.jpg’ },
];
await Book.insertMany(books);
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 books app.get(' /api/books ', async (req, res) => {
try {
// Fetch all books from the database
const allBooks = await Book.find();
// Send the entire books array as JSON response
res.json(allBooks);
} 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 with the following command:
node server.js
Steps to Create the Frontend:
Step 1: Set up React frontend using the command.
npx create-react-app client
cd client
Step 2: Install the required dependencies.
npm i @fortawesome/free-solid-svg-icons
npm i @fortawesome/react-fontawesome
Project Structure:
The updated dependencies in package.json file of frontend will look like:
"dependencies": {
"@fortawesome/free-solid-svg-icons": "^6.5.1",
"@fortawesome/react-fontawesome": "^0.2.0",
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
}
Example: Create the required files and write the following code.
// client/src/App.js import React from 'react' ;
import ProductList from './components/ProductList' ;
import Header from './components/Header' ;
import './App.css' // 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;
import CustomItemContext from './context/ItemContext' ;
const App = () => { return (
<CustomItemContext>
<Header />
<ProductList />
</CustomItemContext>
);
}; export default App;
|
// src/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 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;
|
// 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 Book Store </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;
|
// 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</p>
<p>{product.genre}</p>
<p style={{ fontWeight: "700" , color: "brown" }}>
{product.author}
</p>
<button onClick={() => handleAddToCart(product)}>
Add to Cart
</button>
<button onClick={() => handleRemoveToCart(product)}>-</button>
</div>
</div>
);
}; export default ProductItem;
|
// 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.genre === selectedType
);
setSortedProducts(filtered);
}
};
return (
<div className= "prdt-list" >
<h2 style={{ color: "green" }}>Book 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= "Fiction" >Fiction</option>
<option value= "Dystopian" >Dystopian</option>
{ /* Add more options as needed */ }
</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;
|
/*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: