Open In App

Restaurant Recommendation App using MEAN

Last Updated : 10 May, 2024
Improve
Improve
Like Article
Like
Save
Share
Report

In this article, we’ll explore the process of building a restaurant recommendation application using Angular for the client side and Node.js with Express for the server side. This application will allow users to search for restaurants based on location, cuisine, and rating, providing them with a curated list of dining options.

Project Preview:

Screenshot-2024-05-02-103640

Prerequisites

Approach

The 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 Project

Step 1: Create a new directory for your server-side project:

mkdir server
cd server

Step 2: Initialize a new Node.js project

npm init -y

Step 3: Install the required dependencies

npm install body-parser cores cors dotenv express mongoose --save

Project Structure

Screenshot-2024-05-02-103005

Backend Folder Structure

Updated dependencies will look like:

"dependencies": {
"body-parser": "^1.20.2",
"cores": "^0.8.5",
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"express": "^4.19.2",
"mongoose": "^8.3.2"
}

Creating and managing files and folders:

  • create index.js in the server directory
  • create restaurant.controller.js inside the component folder
  • create Restaurant.model.js inside models folder
  • create restaurant.routes.js insider route folder
JavaScript
// controllers/restaurant.controller.js

const RestaurantModel = require("../models/restaurant.model");

const getRestaurantsByFilters = async (req, res) => {
    console.log("request body is", req.body);
    try {
        const { location, rating, cuisines } = req.body;
        const query = {};
        if (location) {
            query.location = location;
        }

        if (rating?.length > 0) {
            if (rating == 3) {
                query.rating = { $gte: 3, $lte: 5 };
            } else {
                query.rating = { $gte: 4, $lte: 5 };
            }
        }

        if (cuisines?.length > 0) {
            query.cuisines = { $in: cuisines };
        }

        const restaurants = await RestaurantModel.find(query);
        if (restaurants.length > 0) {
            res.json({ success: true, data: restaurants });
        } else {
            res.json({ success: false, message: "No such data found!" });
        }
    } catch (error) {
        res.json({ success: false, error: error.message });
    }
};

module.exports = { getRestaurantsByFilters };
JavaScript
// models/restaurant.model.js

const mongoose = require("mongoose");

const restaurantSchema = new mongoose.Schema({
    name: String,
    address: String,
    contact: String,
    location: String,
    rating: Number,
    offers: Boolean,
    cuisines: [String],
    image: String,
});

module.exports = mongoose.model("restaurant", restaurantSchema);
JavaScript
// routes/restaurant.route.js

const express = require("express");
const router = express.Router();
const { getRestaurantsByFilters } = 
    require("../controllers/restaurant.controller");

router.route("/restaurants").post(getRestaurantsByFilters);

module.exports = router;
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 mongo link";

mongoose
    .connect(MONGO_URL, {
        useNewUrlParser: true,
        useUnifiedTopology: true,
    })
    .then(() => {
        console.log("Connected to MongoDB");
        seedRestaurantData();
        startServer();
    })
    .catch((error) => {
        console.log("Failed to connect to MongoDB", error);
    });

// Define the restaurant model
const restaurantSchema = new mongoose.Schema({
    name: String,
    address: String,
    contact: String,
    location: String,
    rating: Number,
    offers: Boolean,
    cuisines: [String],
    image: String,
});
const RestaurantModel = mongoose.model("Restaurant", restaurantSchema);

async function seedRestaurantData() {
    await RestaurantModel.deleteMany({});

    const restaurantData = [
        {
            name: "Tandoor Palace",
            address: "123 Main Street, Hyderabad",
            contact: "123-456-7890",
            location: "Hyderabad",
            rating: 3.5,
            offers: true,
            cuisines: ["North Indian", "Biryani"],
            image:
                "https://media.geeksforgeeks.org/wp-content/uploads/20240110004646/file.jpg",
        },
        {
            name: "Sushi Samba",
            address: "456 Oak Avenue, Bangalore",
            contact: "987-654-3210",
            location: "Bangalore",
            rating: 4.8,
            offers: true,
            cuisines: ["Japanese", "Sushi"],
            image:
                "https://media.geeksforgeeks.org/wp-content/uploads/20240110004646/file.jpg",
        },
        {
            name: "La Trattoria",
            address: "789 Elm Street, Mumbai",
            contact: "456-789-0123",
            location: "Mumbai",
            rating: 3.2,
            offers: false,
            cuisines: ["Italian", "Pastas", "Pizzas"],
            image:
                "https://media.geeksforgeeks.org/wp-content/uploads/"+
                 "20240110004602/pexels-chan-walrus-958545-(1).jpg",
        },
        {
            name: "Spice Garden",
            address: "321 Oak Road, Delhi",
            contact: "789-012-3456",
            location: "Delhi",
            rating: 2.7,
            offers: true,
            cuisines: ["South Indian", "Vegetarian"],
            image:
                "https://media.geeksforgeeks.org/wp-content/uploads/20240110004646/file.jpg",
        },
        {
            name: "Chopsticks",
            address: "654 Maple Lane, Pune",
            contact: "012-345-6789",
            location: "Pune",
            rating: 4.3,
            offers: true,
            cuisines: ["Chinese", "Asian Fusion"],
            image:
                "https://media.geeksforgeeks.org/wp-content/uploads/"+
                "20240110004602/pexels-chan-walrus-958545-(1).jpg",
        },
        {
            name: "Seaside Grill",
            address: "987 Beach Road, Chennai",
            contact: "345-678-9012",
            location: "Chennai",
            rating: 3.6,
            offers: true,
            cuisines: ["Seafood", "Barbecue"],
            image:
                "https://media.geeksforgeeks.org/wp-content/uploads/20240110004646/file.jpg",
        },
        {
            name: "Pasta Perfetto",
            address: "159 Elm Avenue, Hyderabad",
            contact: "678-901-2345",
            location: "Hyderabad",
            rating: 2.4,
            offers: false,
            cuisines: ["Italian", "Pastas"],
            image:
                "https://media.geeksforgeeks.org/wp-content/uploads/"+
                "20240110004602/pexels-chan-walrus-958545-(1).jpg",
        },
        {
            name: "Sizzle Steak House",
            address: "753 Oak Street, Bangalore",
            contact: "901-234-5678",
            location: "Bangalore",
            rating: 3.9,
            offers: true,
            cuisines: ["Steak", "American"],
            image:
                "https://media.geeksforgeeks.org/wp-content/uploads/20240110004646/file.jpg",
        },
        {
            name: "Dosa Delights",
            address: "369 Main Road, Chennai",
            contact: "234-567-8901",
            location: "Chennai",
            rating: 4.1,
            offers: true,
            cuisines: ["South Indian", "Vegetarian"],
            image:
                "https://media.geeksforgeeks.org/wp-content/uploads/"+
                "20240110004602/pexels-chan-walrus-958545-(1).jpg",
        },
        {
            name: "Bella Napoli",
            address: "852 Pine Avenue, Mumbai",
            contact: "567-890-1234",
            location: "Mumbai",
            rating: 4.6,
            offers: true,
            cuisines: ["Italian", "Pizzas"],
            image:
                "https://media.geeksforgeeks.org/wp-content/uploads/"+
                "20240110004602/pexels-chan-walrus-958545-(1).jpg",
        },
    ];

    await RestaurantModel.insertMany(restaurantData);
    console.log("Restaurant data seeded successfully!");
}

function startServer() {
    app.get("/health", (req, res) => {
        res.status(200)
        .json("Server is up and running");
    });

    app.get("/api/restaurants", async (req, res) => {
        try {
            const restaurants = await RestaurantModel.find({});
            res.status(200).json({ success: true, data: restaurants });
        } 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

Step 4: Run the following command to install Angular CLI globally:

npm install -g @angular/cli@16.0.0

Step 5: Create Angular App

ng new client --version=16.0.0

Project Structure(Frontend)

Screenshot-2024-05-07-224249

Frontend Folder Structure

Manage files and folders

  • create navbar.component.html,navbar.component.ts inside navbar folder
  • create restaurant-card.component.ts, restaurant-card.component.html insider restaurant-card folder
  • create restaurant.module.ts models folder
  • create restaurant.service.ts insider services folder
  • update code of styles.css and main.ts
  • update code of app.component.html , app.component.ts and app.component.css
HTML
<!-- navbar/navbar.component.html -->

<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
    <div class="container">
        <a class="navbar-brand" href="#"><img src={{title}}></a>
        GFG Restaurant Recommendation
        <div class="collapse navbar-collapse" id="navbarNav">
            <ul class="navbar-nav ms-auto">
            </ul>
        </div>
    </div>
</nav>
HTML
<!-- restaurant-card/restaurant-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
<!-- app.component.html -->

<div class="container">
    <app-navbar></app-navbar>
    <div class="filters">
        <div class="location-container">
            <label for="location">Location:</label>
            <select id="location" [formControl]=
            "location" (change)="filterRestaurants()">
                <option value="">Select Location</option>
                <option value="Hyderabad">Hyderabad</option>
                <option value="Bangalore">Bangalore</option>
                <option value="Mumbai">Mumbai</option>
                <option value="Delhi">Delhi</option>
                <option value="Pune">Pune</option>
                <option value="Chennai">Chennai</option>
            </select>
        </div>
        <div class="cuisines-container">
            <label>Cuisines:</label>
            <div *ngFor="let option of options"
             class="cuisine-checkbox">
                <input type="checkbox" [value]="option.value" 
                (change)="toggleCuisine(option.value)" />
                <label>{{ option.label }}</label>
            </div>
        </div>
        <div class="rating-container">
            <label for="rating">Rating:</label>
            <select id="rating" [formControl]="rating"
             (change)="filterRestaurants()">
                <option value="">Select Rating</option>
                <option value="3">3 above</option>
                <option value="4">4 above</option>
            </select>
        </div>
    </div>
    <div class="restaurants">
        <h3>Restaurants</h3>
        <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">
                        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>
        </div>
    </div>
</div>
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;
}
CSS
/* 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
// 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
// restaurant-card/restaurant-card.component.ts

import { Component, Input } from '@angular/core';

@Component({
    selector: 'app-restaurant-card',
    templateUrl: './restaurant-card.component.html',
    standalone: true
})
export class RestaurantCardComponent {
    @Input() restaurant: any;
}
JavaScript
//models/restaurant.model.ts

export interface Restaurant {
    _id?: string;
    name: string;
    address: string;
    contact: string;
    location: string;
    rating: number;
    offers: boolean;
    cuisines: string[];
    image: string;
}
JavaScript
//services/restaurant.service.ts

import { Injectable } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { Observable } from 'rxjs';
import { Restaurant } from '../models/restaurant.model';

@Injectable({
    providedIn: 'root',
})
export class RestaurantService {
    private apiUrl = 'http://localhost:3000/api/restaurants';

    constructor(private http: HttpClient) { }

    getRestaurants(
        location: string = '',
        rating: number = 0,
        sortBy: string = 'name',
        sortOrder: 'asc' | 'desc' = 'asc'
    ): Observable<Restaurant[]> {
        let params = new HttpParams();
        if (location) {
            params = params.set('location', location);
        }
        if (rating > 0) {
            params = params.set('rating', 
            rating.toString());
        }
        params = params.set('sortBy', sortBy);
        params = params.set('sortOrder', sortOrder);

        return this.http.get<Restaurant[]>
        (this.apiUrl, { params });
    }

    createRestaurant(restaurant: Restaurant):
     Observable<Restaurant> {
        return this.http.post<Restaurant>
        (this.apiUrl, restaurant);
    }

    updateRestaurant(
        id: string,
        updatedRestaurant: Restaurant
    ): Observable<Restaurant> {
        return this.http.put<Restaurant>
        (`${this.apiUrl}/${id}`, updatedRestaurant);
    }

    deleteRestaurant(id: string): Observable<void> {
        return this.http.delete<void>
        (`${this.apiUrl}/${id}`);
    }
}
JavaScript
// app.component.ts

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 { RestaurantCardComponent } 
    from './components/restaurant-card/restaurant-card.component';

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css'],
    imports: [
        NavbarComponent,
        RestaurantCardComponent,
        ReactiveFormsModule,
        CommonModule,
    ],
    standalone: true,
})
export class AppComponent implements OnInit {
    title = 'Restro - find your restaurant';
    location = new FormControl('');
    rating = new FormControl('');
    selectedCuisines: string[] = [];
    restaurants: any[] = [];
    filteredRestaurants: any[] = [];

    options = [
        { value: 'North Indian', label: 'North Indian' },
        { value: 'South Indian', label: 'South Indian' },
        { value: 'Chinese', label: 'Chinese' },
        { value: 'Desserts', label: 'Desserts' },
        { value: 'Italian', label: 'Italian' },
        { value: 'Oriental', label: 'Oriental' },
        { value: 'Pastas', label: 'Pastas' },
        { value: 'Pizzas', label: 'Pizzas' },
        { value: 'Japanese', label: 'Japanese' },
        { value: 'Sushi', label: 'Sushi' },
        { value: 'Barbecue', label: 'Barbecue' },
        { value: 'Steak', label: 'Steak' },
        { value: 'Seafood', label: 'Seafood' },
    ];

    constructor(private http: HttpClient) { }

    ngOnInit() {
        this.getRestaurants();
    }

    getRestaurants() {
        this.http.get('http://localhost:4000/api/restaurants').subscribe(
            (response: any) => {
                if (response.success) {
                    this.restaurants = response.data;
                    this.filterRestaurants();
                }
            },
            (error) => {
                console.log('Error:', error);
            }
        );
    }

    filterRestaurants() {
        this.filteredRestaurants = this.restaurants.filter((restaurant) => {
            const locationMatch = this.location.value
                ? restaurant.location === this.location.value
                : true;
            const ratingMatch = this.rating.value
                ? restaurant.rating >= parseInt(this.rating.value)
                : true;
            const cuisineMatch =
                this.selectedCuisines.length > 0
                    ? this.selectedCuisines.every((cuisine) =>
                        restaurant.cuisines.includes(cuisine)
                    )
                    : true;
            return locationMatch && ratingMatch && cuisineMatch;
        });
    }

    toggleCuisine(cuisine: string) {
        const index = this.selectedCuisines.indexOf(cuisine);
        if (index === -1) {
            this.selectedCuisines.push(cuisine);
        } else {
            this.selectedCuisines.splice(index, 1);
        }
        this.filterRestaurants();
    }
}
JavaScript
// src/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

Open your web browser and navigate to http://localhost:4200 to see the running application.

Output

res



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

Similar Reads