Open In App

Bookstore Ecommerce App using MERN Stack

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

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:

Screenshot-2567-01-04-at-233834

Final Project Output

Prerequisites:

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:

Screenshot-2567-01-04-at-234108

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

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('<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:

Screenshot-2567-01-05-at-002713

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

Javascript




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


Javascript




// 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 response = await fetch("http://localhost:5000/api/books");
            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




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


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


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


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.3s 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;
}
 
h2 {
    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: 2rem;
    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:

Untitled-design-(20)

Final output



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

Similar Reads