Bookstore Ecommerce App using MEAN Stack
Last Updated :
08 May, 2024
In this article, we will build a simple yet powerful bookstore application using Angular for the front end and Node.js for the back end. This project application will showcase how to set up a full-stack web application where users can view, filter, and purchase various books.
Project Preview:
Prerequisites
Approach
We will follow a modular approach, separating the frontend and backend components. The frontend will communicate with the backend through HTTP requests to fetch book data and manage the cart.
- Display a list of books with their details like title, author, description, and price.
- Users can add books to their cart and remove them.
- The cart will display the total price and allow users to proceed to checkout.
- Implement filtering and sorting options for better user experience.
Steps to Create Application
Step 1: Create server folder and navigate to server
mkdir bookstore-backend
cd bookstore-backend
Step 2: Initialize the Node Application
npm init -y
Folder Structure(Backend)
Backend Folder Strucure
Step 3: Install the required dependencies.
npm install express mongoose cors body-parser
Updated dependencies looks like
"dependencies": {
"cors": "^2.8.5",
"express": "^4.19.2",
"mongoose": "^8.3.2"
}
Example: Create few file server.js,Book.js,data.jsonbookRoutes.js with following code given below:
JavaScript
//index.js
const express = require('express');
const mongoose = require('mongoose');
const cors = require('cors');
const Book = require('./Book');
const bookRoutes = require('./bookRoutes');
const app = express();
const PORT = process.env.PORT || 5000;
// Middleware
app.use(express.json());
app.use(cors());
// MongoDB connection
mongoose.connect('Your mongo DB link',
{ useNewUrlParser: true, useUnifiedTopology: true })
.then(() => console.log('MongoDB connected'))
.catch(err => console.error(err));
// Seed the database on server startup
const seedDatabase = async () => {
try {
await Book.deleteMany();
const books = require('./data.json');
await Book.insertMany(books);
console.log('Database seeded successfully');
} catch (error) {
console.error('Error seeding database:', error);
}
};
seedDatabase();
// Routes
app.use('/api/books', bookRoutes);
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
JavaScript
//book.js
const mongoose = require('mongoose');
const bookSchema = new mongoose.Schema({
title: String,
author: String,
genre: String,
description: String,
price: Number,
image: String
});
const Book = mongoose.model('Book', bookSchema);
module.exports = Book;
JavaScript
//bookRoutes.js
const express = require('express');
const Book = require('./Book');
const router = express.Router();
router.get('/', async (req, res) => {
try {
const allBooks = await Book.find();
res.json(allBooks);
} catch (error) {
console.error(error);
res.status(500)
.json({ error: 'Internal Server Error' });
}
});
module.exports = router;
JavaScript
//data.json
[
{
"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"
}
]
Start the server using the following command
node index.js
Step 4: Install Angular CLI (if not already installed)
npm install -g @angular/cli
Step 5: Create a new Angular project
ng new client --style=css --routing=false
cd client
Project Structure(Frontend)
Frontend Folder Structure
Dependencies
"dependencies": {
"@angular/animations": "^17.3.0",
"@angular/common": "^17.3.0",
"@angular/compiler": "^17.3.0",
"@angular/core": "^17.3.0",
"@angular/forms": "^17.3.0",
"@angular/platform-browser": "^17.3.0",
"@angular/platform-browser-dynamic": "^17.3.0",
"@angular/router": "^17.3.0",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.14.3"
}
Change and update the files
- create 3 folders insider src/app :components,models and services
- Create files header-product.component.ts and header-product.component.css insider component folder
- Create file book.model.ts inside models folder
- Create file header-cart.service.ts inside services
- create app.module.ts inside app folder
- update code of app.component.ts,main.ts and index.html of app folder
HTML
<!-- src/index.html -->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Bookstore</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root></app-root>
</body>
</html>
CSS
/* components/header-product.component.css */
/* Header Styles */
.navbar {
text-align: center;
background-color: #2c3e50;
color: white;
padding: 20px 0;
}
.navbar-item {
font-size: 24px;
font-weight: bold;
}
.header {
background-color: #34495e;
color: #fff;
padding: 10px;
display: flex;
justify-content: space-between;
align-items: center;
}
.cart {
position: relative;
cursor: pointer;
}
.cart span {
margin-right: 5px;
}
.cart-items {
position: absolute;
top: calc(100% + 5px);
right: 0;
background-color: #2c3e50;
border: 1px solid #34495e;
padding: 10px;
display: flex;
flex-direction: column;
}
.cart-items p {
margin: 5px 0;
}
/* Product List Styles */
.product-list {
display: flex;
flex-wrap: wrap;
justify-content: space-around;
padding: 20px 0;
}
.product-card {
width: 30%;
margin-bottom: 20px;
background-color: #ecf0f1;
border-radius: 10px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
transition: transform 0.3s ease;
}
.product-card img {
width: 100%;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
}
.product-card:hover {
transform: translateY(-5px);
}
.product-details {
padding: 15px;
}
.product-details h3 {
margin-top: 0;
font-size: 20px;
}
.product-details p {
margin: 5px 0;
}
.product-details button {
background-color: #2ecc71;
color: #fff;
border: none;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.3s;
}
.product-details button:hover {
background-color: #27ae60;
}
JavaScript
//components/header-product.component.ts
import { Component, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { HeaderCartService } from '../services/header-cart.service';
import { Book } from '../models/book.model';
@Component({
selector: 'app-header-product',
template: `
<nav class="navbar">
<div class="navbar-brand">
<span class="navbar-item">
GFG Bookstore by Ashish Regmi</span>
</div>
</nav>
<!-- Header template -->
<div class="header">
<!-- Filter and Sort options -->
<div class="options">
<div class="filter-options">
<label for="minPrice">Min Price:</label>
<input type="number" id="minPrice"
[(ngModel)]="minPrice">
<label for="maxPrice">Max Price:</label>
<input type="number" id="maxPrice"
[(ngModel)]="maxPrice">
<button (click)="filterProducts()">Filter</button>
<label for="sort">Sort by:</label>
<select id="sort" [(ngModel)]="sortBy"
(change)="sortProducts()">
<option value="priceLowToHigh">
Price: Low to High</option>
<option value="priceHighToLow">
Price: High to Low</option>
</select>
</div>
</div>
<!-- Cart -->
<div class="cart" (click)="toggleCart() ">
<span>Cart ({{ cartItems.length }})</span>
<div *ngIf="showCart" class="cart-items"
(click)="preventClose($event)">
<div *ngFor="let item of cartItems">
<p>{{ item.title }} - {{ item.price }}</p>
<button (click)="removeFromCart(item)">Remove</button>
</div>
<div>Total Price: {{ calculateTotalPrice() }}</div>
</div>
</div>
</div>
<!-- Product list template -->
<div class="product-list">
<div *ngFor="let product of filteredProducts" class="product-card">
<img [src]="product.image" alt="Book Cover">
<div class="product-details">
<h3>{{ product.title }}</h3>
<p>{{ product.description }}</p>
<p>Price: {{ product.price }} Rs</p>
<button (click)="addToCart(product)">Add to Cart</button>
</div>
</div>
</div>
`,
styleUrls: ['./header-product.component.css']
})
export class HeaderProductComponent implements OnInit {
products: Book[] = [];
cartItems: Book[] = [];
showCart: boolean = false;
totalPrice: number = 0;
minPrice: number = 0;
maxPrice: number = 0;
filteredProducts: Book[] = [];
sortBy: string = 'priceLowToHigh';
constructor(private http: HttpClient,
private cartService: HeaderCartService) { }
ngOnInit(): void {
// Fetch products from the backend
this.http.get<Book[]>('http://localhost:5000/api/books')
.subscribe(products => {
this.products = products;
this.filteredProducts = products;
});
// Subscribe to changes in the cart
this.cartService.cart$.subscribe(cartItems => {
this.cartItems = cartItems;
});
}
addToCart(product: Book) {
this.cartService.addToCart(product);
this.calculateTotalPrice();
}
removeFromCart(product: Book) {
this.cartService.removeFromCart(product);
this.calculateTotalPrice();
}
toggleCart() {
this.showCart = !this.showCart;
}
calculateTotalPrice(): number {
return this.cartItems.reduce((total, item) =>
total + item.price, 0);
}
filterProducts() {
this.filteredProducts = this.products.filter(product => {
return product.price >= this.minPrice &&
product.price <= this.maxPrice;
});
this.sortProducts();
}
sortProducts() {
if (this.sortBy === 'priceLowToHigh') {
this.filteredProducts.sort((a, b) => a.price - b.price);
} else if (this.sortBy === 'priceHighToLow') {
this.filteredProducts.sort((a, b) => b.price - a.price);
}
}
preventClose(event: MouseEvent) {
event.stopPropagation();
}
}
JavaScript
// models/book.model.ts
export interface Book {
_id: string;
title: string;
author: string;
genre: string;
description: string;
price: number;
image: string;
}
JavaScript
// services/header-cart.service.ts
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { HttpClient } from '@angular/common/http';
import { Book } from '../models/book.model';
@Injectable({
providedIn: 'root'
})
export class HeaderCartService {
private cart: Book[] = [];
private cartSubject = new BehaviorSubject
<Book[]>(this.cart);
cart$ = this.cartSubject.asObservable();
constructor(private http: HttpClient) { }
addToCart(book: Book) {
this.cart.push(book);
this.cartSubject.next(this.cart);
}
removeFromCart(book: Book) {
const index = this.cart.findIndex
(item => item._id === book._id);
if (index !== -1) {
this.cart.splice(index, 1);
this.cartSubject.next(this.cart);
}
}
}
JavaScript
// client/src/app/app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: `
<app-header-product></app-header-product>
`,
})
export class AppComponent { }
JavaScript
// src/app/app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { AppComponent } from './app.component';
import { HeaderProductComponent } from './components/header-product.component';
import { HeaderCartService } from './services/header-cart.service';
@NgModule({
declarations: [
AppComponent,
HeaderProductComponent
],
imports: [
BrowserModule,
FormsModule, // Add FormsModule here
HttpClientModule
],
providers: [HeaderCartService],
bootstrap: [AppComponent]
})
export class AppModule { }
JavaScript
// client/src/main.ts
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.error(err));
Start the Angular App using the following command.
ng serve
Output
Share your thoughts in the comments
Please Login to comment...