In this comprehensive guide, we’ll walk through the step-by-step process of creating a Fruit and Vegetable Market Shop using the MEAN (MongoDB, Express.js, Angular, 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.
-(1)-2-(1)-(1)-(1).png) Final Project Output Prerequisites:ApproachThe project will be divided into two main parts:
Client-side: The client-side will be built using Angular, a popular JavaScript framework for building web applications. We’ll create the necessary components, services, and routing to handle the user interface and interact with the server-side.
Server-side: The server-side will be built using Node.js and Express, a web application framework for Node.js. We’ll create the API endpoints, handle database operations using Mongoose, and manage Cross-Origin Resource Sharing (CORS) policies.
Steps to Create the ProjectStep 1: Create a root directory(MEAN) for your project
mkdir Fruits cd Fruits Step 2: Create a root directory for your server-side project.
mkdir server cd server Step 3: Initialize a new Node.js project
npm init -y Step 4: Install the required dependencies
npm install cors dotenv express mongoose Project Structure (Backend):
.png) Backend Structure Updated dependencies will look like:{ "name": "server", "version": "1.0.0", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "description": "", "dependencies": { "cors": "^2.8.5", "express": "^4.19.2", "mongoose": "^8.4.0" } }
Example: To demonstrate creating the server endpoint which is consumed by the front-end application.
JavaScript
//index.js
const express = require("express");
const mongoose = require("mongoose");
const cors = require("cors");
const app = express();
app.use(express.json());
app.use(cors());
// Connect to MongoDB
const MONGO_URL = `your db url`;
mongoose
.connect(MONGO_URL)
.then(() => {
console.log("Connected to MongoDB");
seedProductsData();
})
.catch((error) => {
console.log("Failed to connect to MongoDB", error);
});
const productsSchema = new mongoose.Schema({
name: String,
description: String,
price: Number,
type: String,
image: String,
});
const ProductsModel = mongoose.model("Products", productsSchema);
async function seedProductsData() {
await ProductsModel.deleteMany({});
const productsData = [
{
name: "Apple",
type: "Fruit",
description: "Fresh and crispy",
price: 150,
image:
"https://media.geeksforgeeks.org/wp-content/uploads/20240104142542/apple.jpg",
},
{
name: "Banana",
type: "Fruit",
description: "Rich in potassium",
price: 75,
image:
"https://media.geeksforgeeks.org/wp-content/uploads/20240104142554/banana.jpg",
},
{
name: "Orange",
type: "Fruit",
description: "Packed with vitamin C",
price: 200,
image:
"https://media.geeksforgeeks.org/wp-content/uploads/20240104142641/orange.jpg",
},
{
name: "Carrot",
type: "Vegetable",
description: "Healthy and crunchy",
price: 100,
image:
"https://media.geeksforgeeks.org/wp-content/uploads/20240104142613/carrot.jpg",
},
{
name: "Broccoli",
type: "Vegetable",
description: "Nutrient-rich greens",
price: 175,
image:
"https://media.geeksforgeeks.org/wp-content/uploads/20240104142601/brocoli.jpg",
},
{
name: "Grapes",
type: "Fruit",
description: "Sweet and juicy",
price: 250,
image:
"https://media.geeksforgeeks.org/wp-content/uploads/20240104142629/grapes.jpg",
},
{
name: "Strawberry",
type: "Fruit",
description: "Delicious red berries",
price: 300,
image:
"https://media.geeksforgeeks.org/wp-content/uploads/20240104142657/strawberry.jpg",
},
{
name: "Lettuce",
type: "Vegetable",
description: "Crisp and fresh",
price: 120,
image:
"https://media.geeksforgeeks.org/wp-content/uploads/20240104142635/lettue.jpg",
},
{
name: "Tomato",
type: "Vegetable",
description: "Versatile and flavorful",
price: 180,
image:
"https://media.geeksforgeeks.org/wp-content/uploads/20240104142704/tomato.jpg",
},
{
name: "Cucumber",
type: "Vegetable",
description: "Cool and hydrating",
price: 130,
image:
"https://media.geeksforgeeks.org/wp-content/uploads/20240104142621/cocumber.jpg",
},
];
await ProductsModel.insertMany(productsData);
console.log("Products data seeded successfully!");
}
app.get("/api/products", async (req, res) => {
try {
const products = await ProductsModel.find({});
res.status(200).json({ success: true, data: products });
} catch (error) {
res.status(500).json({ success: false, error: error.message });
}
});
const PORT = process.env.PORT || 4000;
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
To start the server run the following command.node index.js Steps to create Front EndStep 1: Run the following command to install Angular CLI globally:
npm i @angular/[email protected] Step 2: Go to Fruits directory and run this command
mkdir client Step 3: Create Angular App
ng new fruit-vegetable-market-app Updated dependencies will look like:{ "name": "fruit-vegetable-market-app", "version": "0.0.0", "scripts": { "ng": "ng", "start": "ng serve", "build": "ng build", "watch": "ng build --watch --configuration development", "test": "ng test" }, "private": true, "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" }, "devDependencies": { "@angular-devkit/build-angular": "^17.3.7", "@angular/cli": "^17.3.7", "@angular/compiler-cli": "^17.3.0", "@types/jasmine": "~5.1.0", "jasmine-core": "~5.1.0", "karma": "~6.4.0", "karma-chrome-launcher": "~3.2.0", "karma-coverage": "~2.2.0", "karma-jasmine": "~5.1.0", "karma-jasmine-html-reporter": "~2.1.0", "typescript": "~5.4.2" } }
Folder Structure:.png) Front end structure Stpes to create the front-end of the projectStep 1: Create navbar.component.html,navbar.component.ts inside navbar folder
Step 2: Create product-card.component.ts, product-card.component.html insider product-card folder
Step 3: Update code of styles.css and main.ts
Step 4: Update code of app.component.html , app.component.ts and app.component.css
HTML
<!-- app.component.html -->
<div class="container">
<app-navbar></app-navbar>
<div class="filters">
<div class="rating-container">
<label for="rating">Filter by Type:</label>
<select id="rating" [formControl]="rating" (change)="filterRestaurants()">
<option value="">All</option>
<option value="Fruit">Fruit</option>
<option value="Vegetable">Vegetable</option>
</select>
</div>
<div>
<button (click)="sortByPrice()">Sort by Price</button>
<label for="minPrice">Min Price:</label>
<input
type="number"
id="minPrice"
[formControl]="minPrice"
(change)="filterRestaurants()"
/>
<label for="maxPrice">Max Price:</label>
<input
type="number"
id="maxPrice"
[formControl]="maxPrice"
(change)="filterRestaurants()"
/>
<button (click)="filterByRange()">Filter by Range</button>
</div>
<div>
Search :
<input
type="text"
[(ngModel)]="searchText"
(keyup)="applySearch()"
placeholder="Search..."
/>
</div>
</div>
<div class="restaurants">
<h3>Restaurants</h3>
<span class="cart">
Cart
<div className="cart-content">
<span style="color: brown">Total Price : $ {{ cartTotal }}</span>
</div>
</span>
<div class="restaurant-cards">
<div
*ngFor="let restaurant of filteredRestaurants"
class="restaurant-card"
>
<img [src]="restaurant.image" alt="{{ restaurant.name }}" />
<div class="card-body">
<h5 class="card-title">{{ restaurant.name }}</h5>
<p class="card-text">
{{ restaurant.description }}
</p>
</div>
<ul class="list-group list-group-flush">
<li class="list-group-item">Price: {{ restaurant.price }} Rs/Kg</li>
</ul>
<button class="btn1" (click)="addToCart(restaurant.price)">
Add to Cart</button
>{{ " " }}
<button (click)="removeFromCart(restaurant.price)">-</button>
</div>
</div>
</div>
</div>
HTML
<!-- navbar/navbar.component.html -->
<nav class="navbar navbar-expand-lg" style="background-color: green;">
<div class="container" >
<a class="navbar-brand" href="#"><img src={{title}}></a>
Fruit And Vegetable Market using MEAN
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto">
</ul>
</div>
</div>
</nav>
HTML
<!-- product-card/product-card.component.html -->
<div class="card">
<img [src]="restaurant.image"
class="card-img-top" [alt]="restaurant.name">
<div class="card-body">
<h5 class="card-title">{{ restaurant.name }}</h5>
<p class="card-text">
Types of food we offer: {{ restaurant.cuisines.join(', ') }}
</p>
</div>
<ul class="list-group list-group-flush">
<li class="list-group-item">Address: {{ restaurant.address }}</li>
<li class="list-group-item">City: {{ restaurant.location }}</li>
<li class="list-group-item">Rating: {{ restaurant.rating }}</li>
</ul>
</div>
HTML
<!-- index.html -->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>ResturentApp</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<!-- Latest compiled and minified CSS -->
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="https://unpkg.com/[email protected]/components/blogs/blog-2/assets/css/blog-2.css">
</head>
<body>
<app-root></app-root>
</body>
</html>
CSS
/* app.component.css */
.headers {
display: flex;
align-items: center;
justify-content: space-between;
margin-left: 5vw;
margin-right: 5vw;
margin-top: 3vh;
padding-bottom: 2vh;
border-bottom: 1px solid gray;
}
.location-container {
width: 10vw;
}
.cuisines-container {
margin-top: 5vh;
width: 15vw;
height: 10vh;
overflow-y: scroll;
}
.cuisine-checkbox {
display: flex;
align-items: center;
margin-bottom: 0.5rem;
}
.cuisine-checkbox input {
margin-right: 0.5rem;
}
.rating-container {
width: 10vw;
}
.restaurants {
padding-bottom: 3vh;
}
.restaurants h3 {
margin-left: 5vw;
margin-top: 1vh;
margin-bottom: 1vh;
}
.restaurant-cards {
display: flex;
flex-wrap: wrap;
margin-left: 5vw;
}
.restaurant-card {
width: 18rem;
margin-left: 4vw;
margin-top: 4vh;
}
.restaurant-card img {
height: 20vh;
background-size: cover;
}
.btn1{
margin: 10px;
}
.cart{
position: fixed;
top: 250px;
right: 10px;
width: 200px;
border: 2px solid #4CAF50;
border-radius: 10px;
height: 20vh;
background-color: #fff;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
overflow-y: auto;
z-index: 1000;
}
.cart-content{
padding: 16px;
}
CSS
/* You can add global styles to this file, and also import other style files */
/* styles.css */
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap');
body {
font-family: 'Poppins', sans-serif;
background-color: #f5f5f5;
color: #333;
margin: 0;
padding: 0;
}
.container {
max-width: 1600px;
margin: 0 auto;
padding: 1rem;
}
.navbar {
font-size: 25px;
background-color: #333;
color: white;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.navbar-brand {
font-size: 1.5rem;
font-weight: 700;
text-decoration: none;
}
.filters {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 2rem;
padding-bottom: 2rem;
border-bottom: 1px solid #ddd;
}
.location-container,
.rating-container {
width: 15%;
}
.cuisines-container {
width: 30%;
max-height: 200px;
overflow-y: auto;
}
.cuisine-checkbox {
display: flex;
align-items: center;
margin-bottom: 0.5rem;
}
.cuisine-checkbox input {
margin-right: 0.5rem;
}
.restaurants {
margin-top: 2rem;
}
.restaurants h3 {
font-size: 1.75rem;
font-weight: 600;
margin-bottom: 1.5rem;
}
.restaurant-cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
grid-gap: 2rem;
}
.restaurant-card {
background-color: #fff;
border-radius: 0.5rem;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
overflow: hidden;
transition: transform 0.3s ease-in-out;
}
.restaurant-card:hover {
transform: translateY(-5px);
box-shadow: 0 8px 12px rgba(0, 0, 0, 0.15);
}
.restaurant-card img {
height: 100%;
width: 90%;
/* object-fit: contain; */
/* object-position: center; */
margin-left: 15px;
}
.card-body {
padding: 1.5rem;
}
.card-title {
font-size: 1.25rem;
font-weight: 600;
margin-bottom: 0.5rem;
}
.card-text {
font-size: 0.9rem;
color: #666;
margin-bottom: 1rem;
}
.list-group-item {
font-size: 0.9rem;
padding: 0.75rem 1.5rem;
border-bottom: 1px solid #f1f1f1;
}
.list-group-item:last-child {
border-bottom: none;
}
JavaScript
// appConfig.config.js
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [provideRouter(routes)]
};
JavaScript
// app.component.ts
import { NgModule } from '@angular/core';
import { Component, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
import { NavbarComponent }
from './components/navbar/navbar.component';
import { ResturentCardComponent }
from './components/product-card/product-card.component';
import { FormsModule } from '@angular/forms';
import { Router } from '@angular/router';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
imports: [
NavbarComponent,
ResturentCardComponent,
ReactiveFormsModule,
CommonModule,
FormsModule,
],
standalone: true,
})
export class AppComponent implements OnInit {
title = 'Restro - find your restaurant';
rating = new FormControl('');
restaurants: any[] = [];
filteredRestaurants: any[] = [];
searchResults: string[] = [];
searchText: string = '';
cartTotal: number = 0;
minPrice = new FormControl('');
maxPrice = new FormControl('');
minFilterPrice: number | null = null;
maxFilterPrice: number | null = null;
constructor(private http: HttpClient, private router: Router) { }
ngOnInit() {
this.getRestaurants();
}
getRestaurants() {
this.http.get('http://localhost:4000/api/products').subscribe(
(response: any) => {
if (response.success) {
this.restaurants = response.data;
this.filterRestaurants();
}
},
(error) => {
console.log('Error:', error);
}
);
}
filterRestaurants() {
this.filteredRestaurants = this.restaurants.filter((restaurant) => {
let passType = true;
if (this.rating.value === 'Fruit') {
passType = restaurant.type === 'Fruit';
} else if (this.rating.value === 'Vegetable') {
passType = restaurant.type === 'Vegetable';
}
let passPriceRange = true;
if (this.minFilterPrice !== null) {
passPriceRange = restaurant.price >= this.minFilterPrice;
}
if (this.maxFilterPrice !== null) {
passPriceRange = passPriceRange && restaurant.price <= this.maxFilterPrice;
}
return passType && passPriceRange;
});
}
filterByRange() {
const minValue = this.minPrice.value;
const maxValue = this.maxPrice.value;
if (minValue !== null && maxValue !== null) {
const minPrice = +minValue;
const maxPrice = +maxValue;
if (!isNaN(minPrice) && !isNaN(maxPrice) && maxPrice >= minPrice) {
this.minFilterPrice = minPrice;
this.maxFilterPrice = maxPrice;
this.filterRestaurants();
} else {
alert("Invalid price range");
}
}
}
applySearch(): void {
this.filteredRestaurants = this.restaurants.filter(restaurant =>
restaurant.name.toLowerCase().includes(this.searchText.toLowerCase())
);
}
addToCart(price: number): void {
this.cartTotal += price;
}
removeFromCart(price: number): void {
if (this.cartTotal > 0) {
this.cartTotal -= price;
} else if(this.cartTotal === 0) {
// Optionally handle negative total or alert user
alert("No Item")
return;
}
}
sortByPrice() {
this.filteredRestaurants.sort((a, b) => a.price - b.price);
}
}
JavaScript
// product-card/product-card.component.ts
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-resturent-card',
templateUrl: './product-card.component.html',
standalone: true
})
export class ResturentCardComponent {
@Input() restaurant: any;
}
JavaScript
// navbar/navbar.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-navbar',
templateUrl: './navbar.component.html',
standalone: true
})
export class NavbarComponent {
title = 'https://media.geeksforgeeks.org/gfg-gg-logo.svg';
}
JavaScript
// main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { importProvidersFrom } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { AppComponent } from './app/app.component';
bootstrapApplication(AppComponent, {
providers: [
importProvidersFrom(HttpClientModule)
]
})
.catch(err => console.error(err));
Start the Angular App using the following command.ng serve Output:
 Final Project Output
|