Open In App

Restaurant App using MERN Stack

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

Creating a Restaurant app will cover a lot of features of the MERN stack. In this tutorial, we’ll guide you through the process of creating a restaurant application using the MERN stack. The application will allow users to browse through a list of restaurants, view their menus, and add items to a shopping cart.

Preview of final output: Let us have a look at how the final output will look like.

asdfg

Project Preview

Prerequisites:

Approach to create Restaurant App using MERN:

1. Import Statements:

  • Import necessary dependencies and components.
  • React is imported for defining React components.
  • RestaurantList, RestaurantCard, DishesMenu, DishCard and Cart are custom components, assumed to be present in the ./components directory.
  • RestaurantContext is imported, presumably a custom context provider.

2.Functional Component:

  • Define a functional component named App.

3.Context Provider:

  • Wrap the App component inside the RestaurantContext provider. This suggests that the components within this provider have access to the context provided by RestaurantContext.

4.Component Rendering:

Render the following components:

  • RestaurantContext: Presumably, this is a context provider that wraps its child components (App). The purpose of this context is not clear from the provided code snippet.
  • All other components such as RestaurantList and DishesMenu is wrapped inside App component so it also has the access of RestaurantContext.
  • RestaurantList wraps RestaurantCard

Steps to create the Project

Step 1: creating a project folder.

mkdir restaurant-app
cd restaurant-app

Step 2: Backend Setup

Create folder within restaurant-app folder i.e. server

mkdir server 
cd server

Step 3: Initialize the Express project and install the required dependencies.

npm init -y
npm i express cors mongoose

Folder Structure(Backend):

rewty

Backend

The updated dependencies in package.json file of backend will look like:

"dependencies": {
"cors": "^2.8.5",
"express": "^4.18.2",
"mongoose": "^8.0.4",
"nodemon": "^3.0.2"
}

Example: Now write the codes in the respective files.

Javascript




//server.js
 
const express = require("express");
const mongoose = require("mongoose");
const cors = require("cors");
 
const app = express();
const PORT = process.env.PORT || 5000;
 
app.use(cors());
app.use(express.json());
 
mongoose.connect("Your mongodb connection string", {
    useNewUrlParser: true,
    useUnifiedTopology: true,
});
 
const restaurantSchema = new mongoose.Schema({
    name: String,
    image: String,
    menu: [
        {
            name: String,
            price: Number,
            image: String,
        },
    ],
    rating: Number,
});
 
const Restaurant = mongoose.model("Restaurant", restaurantSchema);
 
// Seed initial data
const seedData = [
    {
        name: "Italian Delight",
        menu: [
            {
                name: "Pasta Alfredo",
                price: 10,
            },
            {
                name: "Margherita Pizza",
                price: 15,
            },
            {
                name: "Chicken Parmesan",
                price: 20,
            },
        ],
        rating: 4.5,
    },
    {
        name: "Seafood Paradise",
        menu: [
            {
                name: "Grilled Salmon",
                price: 12,
            },
            {
                name: "Lobster Bisque",
                price: 18,
            },
            {
                name: "Shrimp Scampi",
                price: 25,
            },
        ],
        rating: 3.8,
    },
    {
        name: "Vegetarian Haven",
        menu: [
            {
                name: "Quinoa Salad",
                price: 8,
            },
            {
                name: "Eggplant Parmesan",
                price: 12,
            },
            {
                name: "Mushroom Risotto",
                price: 16,
            },
        ],
        rating: 4.2,
    },
    {
        name: "Sizzling Steakhouse",
        menu: [
            {
                name: "Filet Mignon",
                price: 22,
            },
            {
                name: "New York Strip",
                price: 18,
            },
            {
                name: "Ribeye Steak",
                price: 25,
            },
        ],
        rating: 4.7,
    },
    {
        name: "Asian Fusion",
        menu: [
            {
                name: "Sushi Platter",
                price: 20,
            },
            {
                name: "Pad Thai",
                price: 15,
            },
            {
                name: "Mongolian Beef",
                price: 18,
            },
        ],
        rating: 4.0,
    },
];
 
const seedDatabase = async () => {
    try {
        await Restaurant.deleteMany(); // Clear existing data
        await Restaurant.insertMany(seedData);
        console.log("Database seeded successfully.");
    } catch (error) {
        console.error("Error seeding the database:", error.message);
    }
};
 
// Seed data when the server starts
seedDatabase();
 
app.get("/restaurants", async (req, res) => {
    try {
        // Use the 'find' method of the 'Restaurant' model to retrieve all restaurants
        const restaurants = await Restaurant.find({});
 
        // Send the retrieved restaurants as a JSON response
        res.json(restaurants);
    } catch (error) {
        // Handle any errors that may occur during the process and send a 500 Internal Server Error response
        res.status(500).json({ error: error.message });
    }
});
 
app.listen(PORT, () => {
    console.log(`Server is running on port ${PORT}`);
});


Step 4: Initialize the frontend app and install required dependencies.

npx create-react-app client
cd client
npm i axios

Project Structure (Frontend):

Screenshot-2567-01-07-at-234938

Frontend Structure

The updated dependencies in package.json file of frontend will look like:

 "dependencies": {
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"axios": "^1.6.5",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-query": "^3.39.3",
"react-router-dom": "^6.21.1",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
}

Example: Create the respective folders and files according to the folder strure give and add the following codes.

Javascript




// App.js
 
import React, { useContext } from 'react';
import RestaurantList from './components/RestaurantList';
import DishesMenu from './components/DishesMenu';
import Cart from './components/Cart';
import { RestaurantContext } from './contexts/RestaurantContext';
import './App.css'// Import the CSS file
 
const App = () => {
  const { selectedRestaurant } = useContext(RestaurantContext);
 
  return (
    <>
      <div className="container">
        <h1 className="header">GFG Restaurant App</h1>
        <Cart style={{position:"absolute",right:"20px",top:"20px"}} />
        <RestaurantList />
        {selectedRestaurant && <DishesMenu />}
      </div>
    </>
  );
};
 
export default App;


Javascript




//index.js (update previous index.js)
 
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { RestaurantProvider } from './contexts/RestaurantContext';
 
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
    <RestaurantProvider>
        <App />
    </RestaurantProvider>
);
 
reportWebVitals();


Javascript




//contexts/RestaurantContext.js
 
import React, { createContext, useState, useEffect } from "react";
import axios from "axios";
 
const RestaurantContext = createContext();
 
const RestaurantProvider = ({ children }) => {
    const [restaurants, setRestaurants] = useState([]);
    const [selectedRestaurant, setSelectedRestaurant] = useState(null);
    const [cartItems, setCartItems] = useState([]);
    const [totalPrice, setTotalPrice] = useState(0);
 
    useEffect(() => {
        const fetchRestaurants = async () => {
            try {
                const response = await axios.get(
                    "http://localhost:5000/restaurants"
                );
                setRestaurants(response.data);
            } catch (error) {
                console.error("Error fetching restaurants:", error.message);
            }
        };
 
        fetchRestaurants();
    }, []);
 
    const handleAddItems = (dish) => {
        console.log("Dish:", dish);
 
        // Check if the dish already exists in the cart
        const existingItemIndex = cartItems.findIndex(
            (item) => item._id === dish._id
        );
 
        if (existingItemIndex !== -1) {
            // If the dish already exists, update the
            // quantity or any other logic
            console.log(
                `Dish already exists in the cart.
                You may want to update the quantity.`
            );
            // Example: Increment the quantity
            const updatedCartItems = [...cartItems];
            updatedCartItems[existingItemIndex] = {
                ...updatedCartItems[existingItemIndex],
                quantity: updatedCartItems[existingItemIndex].quantity + 1,
            };
            //   console.log('cart',cartItems.length);
            //    setTotalPrice(prev=>prev-dish.price)
 
            setCartItems(updatedCartItems);
        } else {
            // If the dish is not in the cart, add it
            console.log("Dish does not exist in the cart. Adding to the cart.");
            console.log("cart", cartItems.length);
            //   setTotalPrice(prev=>prev-dish.price)
 
            setCartItems([...cartItems, { ...dish, quantity: 1 }]);
        }
        setTotalPrice((prev) => prev + dish.price);
    };
 
    const handleRemoveItems = (dish) => {
        console.log("Dish ID to remove:", dish);
 
        // Check if the dish exists in the cart
        const existingItemIndex = cartItems.findIndex(
            (item) => item._id === dish._id
        );
 
        if (existingItemIndex !== -1) {
            // If the dish exists, decrement the quantity
            // or remove it from the cart
            console.log(
                `Dish exists in the cart. You may want
                 to decrease the quantity or remove it.`
            );
 
            const updatedCartItems = [...cartItems];
 
            if (updatedCartItems[existingItemIndex].quantity > 1) {
                // If the quantity is greater than 1, decrement the quantity
                updatedCartItems[existingItemIndex] = {
                    ...updatedCartItems[existingItemIndex],
                    quantity: updatedCartItems[existingItemIndex].quantity - 1,
                };
            } else {
                // If the quantity is 1, remove the dish from the cart
                updatedCartItems.splice(existingItemIndex, 1);
            }
 
            setCartItems(updatedCartItems);
            setTotalPrice((prev) => prev - dish.price);
        } else {
            // If the dish is not in the cart,
            // log a message or handle accordingly
            console.log("Dish does not exist in the cart.");
        }
    };
 
    const value = {
        restaurants,
        selectedRestaurant,
        setSelectedRestaurant,
        handleAddItems,
        handleRemoveItems,
        totalPrice,
    };
 
    return (
        <RestaurantContext.Provider value={value}>
            {children}
        </RestaurantContext.Provider>
    );
};
 
export { RestaurantContext, RestaurantProvider };


Javascript




//components/RestaurantList.js
 
import React, { useContext, useState, useEffect } from "react";
import RestaurantCard from "./RestaurantCard";
import { RestaurantContext } from "../contexts/RestaurantContext";
 
const RestaurantList = () => {
    const { restaurants, setSelectedRestaurant } =
        useContext(RestaurantContext);
    const [filteredRestaurants, setFilteredRestaurants] = useState([
        ...restaurants,
    ]);
    const [ratingFilter, setRatingFilter] = useState("");
    const [searchTerm, setSearchTerm] = useState("");
 
    useEffect(() => {
        filterRestaurants();
    }, [ratingFilter, searchTerm, restaurants]);
 
    const handleRestaurantClick = (restaurantId) => {
        setSelectedRestaurant(
            restaurants.find((restaurant) => restaurant._id === restaurantId)
        );
    };
 
    const handleRatingChange = (e) => {
        setRatingFilter(e.target.value);
    };
 
    const handleSearchChange = (e) => {
        setSearchTerm(e.target.value);
    };
 
    const filterRestaurants = () => {
        let filtered = restaurants;
 
        if (ratingFilter) {
            filtered = filtered.filter(
                (restaurant) => restaurant.rating >= parseFloat(ratingFilter)
            );
        }
 
        if (searchTerm) {
            const searchLower = searchTerm.toLowerCase();
            filtered = filtered.filter((restaurant) =>
                restaurant.name.toLowerCase().includes(searchLower)
            );
        }
 
        setFilteredRestaurants(filtered);
    };
 
    return (
        <div className="container">
            <h2 className="header">Restaurant List</h2>
            <div className="filter-container">
                <label htmlFor="rating" className="filter-label">
                    Filter by Rating:
                </label>
                <input
                    type="number"
                    id="rating"
                    value={ratingFilter}
                    onChange={handleRatingChange}
                    className="filter-input"
                />
                <label htmlFor="search" className="filter-label">
                    Search by Name:
                </label>
                <input
                    type="text"
                    id="search"
                    value={searchTerm}
                    onChange={handleSearchChange}
                    className="filter-input"
                />
            </div>
            <div className="restaurant-card-container">
                {filteredRestaurants.map((restaurant) => (
                    <RestaurantCard
                        key={restaurant._id}
                        restaurant={restaurant}
                        onClick={() => handleRestaurantClick(restaurant._id)}
                    />
                ))}
            </div>
        </div>
    );
};
 
export default RestaurantList;


Javascript




//components/RestaurantCard.js
 
import React from "react";
 
const RestaurantCard = ({ restaurant, onClick }) => {
    return (
        <div className="card" onClick={onClick}>
            <h3>{restaurant.name}</h3>
            <div className="image-container">
                <img
                    className="restaurant-image"
                    src={restaurant.image}
                    alt={restaurant.name}
                />
            </div>
            <p>Rating: {restaurant.rating}</p>
        </div>
    );
};
 
export default RestaurantCard;


Javascript




//components/DishesMenu.js
 
import React, { useContext } from 'react';
import DishCard from './DishCard';
import { RestaurantContext } from '../contexts/RestaurantContext';
 
const DishesMenu = () => {
    const { selectedRestaurant } = useContext(RestaurantContext);
 
    return (
        <div>
            <h2>Menu</h2>
            {selectedRestaurant && (
                <div style={{ display: 'flex', flexWrap: 'wrap' }}>
                    {selectedRestaurant.menu.map((dish) => (
                        <DishCard key={dish.name} dish={dish} />
                    ))}
                </div>
            )}
        </div>
    );
};
 
export default DishesMenu;
            )}
        </div>
    );
};
 
export default DishesMenu;


Javascript




//components/DishCard.js
 
import React, { useContext } from "react";
import { RestaurantContext } from "../contexts/RestaurantContext";
 
const DishCard = ({ dish }) => {
    const { handleAddItems, handleRemoveItems } = useContext(RestaurantContext);
 
    const handleAdd = () => {
        handleAddItems(dish);
    };
 
    const handleRemove = () => {
        handleRemoveItems(dish);
    };
 
    return (
        <div className="dish-card">
            <h3>{dish.name}</h3>
            <img src={dish.image} alt="" />
            <p>Price: ${dish.price}</p>
 
            <div
                style={{
                    width: "40%",
                    display: "flex",
                    justifyContent: "space-between",
                    alignItems: "center",
                }}
            >
                <button onClick={handleAdd}>+</button>
                <button onClick={handleRemove}>-</button>
            </div>
        </div>
    );
};
 
export default DishCard;


Javascript




//components/Cart.js
 
import React, { useContext } from 'react';
import { RestaurantContext } from '../contexts/RestaurantContext';
 
 
const Cart = () => {
    const { totalPrice } = useContext(RestaurantContext);
 
    return (
        <div className="cart-container">
            <h2>Cart</h2>
            <div className="cart-content">
                <span style={{ color: "brown" }}>Total Price: </span> ${totalPrice}
                {/* Add other cart items here */}
            </div>
        </div>
    );
};
 
export default Cart;


CSS




/* App.css */
 
.container {
    font-family: 'Arial, sans-serif';
    padding: 20px;
    display: flex;
    flex-direction: column;
    align-items: center;
}
 
.header {
    font-size: 24px;
    margin-bottom: 10px;
 
    border-radius: 15px;
}
 
/* Styles to Resturant List  */
 
 
/* RestaurantList.css */
 
.container {
    font-family: 'Arial, sans-serif';
    padding: 20px;
    display: flex;
    flex-direction: column;
    align-items: center;
}
 
.header {
    font-size: 32px;
    margin-bottom: 20px;
    background-color: #4caf50;
    color: white;
    padding: 10px;
}
 
.filter-container {
    display: flex;
    gap: 20px;
    margin-bottom: 20px;
}
 
.filter-container label {
    font-size: 18px;
    color: #555;
}
 
.filter-input {
    padding: 8px;
    font-size: 16px;
}
 
.restaurant-card-container {
    display: flex;
    flex-wrap: wrap;
    gap: 20px;
    justify-content: center;
}
 
 
/* RestaurantCard.css */
 
.card {
    border: 1px solid #ccc;
    border-radius: 8px;
    padding: 12px;
    margin: 10px;
    width: 200px;
    cursor: pointer;
    transition: transform 0.3s ease-in-out;
}
 
.card:hover {
    transform: scale(1.05);
}
 
.image-container {
    overflow: hidden;
    border-radius: 8px;
    margin-bottom: 10px;
    height: 150px;
    /* Set a fixed height for the image container */
}
 
.restaurant-image {
    width: 100%;
    height: 100%;
    object-fit: cover;
    /* Maintain the aspect ratio and cover the container */
    border-radius: 8px;
}
 
/* Responsive Styles */
@media screen and (max-width: 600px) {
    .card {
        width: 100%;
    }
}
 
 
 
/* Dish Card  */
.dish-card {
    border: 1px solid #ccc;
    border-radius: 8px;
    padding: 12px;
    margin: 10px;
    width: 200px;
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    transition: transform 0.3s ease-in-out;
}
 
.dish-card:hover {
    transform: scale(1.05);
}
 
img {
    width: 100%;
    /* Set the width to fill the container */
    height: 100px;
    /* Set the fixed height for the image */
    object-fit: cover;
    /* Maintain the aspect ratio and cover the container */
    border-radius: 8px;
    margin-bottom: 10px;
}
 
h3 {
    margin-bottom: 8px;
}
 
p {
    margin-bottom: 8px;
}
 
button {
    margin-top: 8px;
    padding: 8px;
    cursor: pointer;
    background-color: #4caf50;
    color: white;
    border: none;
    border-radius: 4px;
}
 
/* Responsive Styles */
@media screen and (max-width: 600px) {
    .dish-card {
        width: 100%;
    }
}
 
 
/* CART  */
.cart-container {
    position: fixed;
    top: 10px;
    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;
}
 
h2 {
    margin-bottom: 10px;
    margin-left: 20px;
}
 
.cart-content {
    padding: 16px;
}


Step to Run the code by typing the following command

  • Start the backend:
node server.js
  • Start the frontend:
npm start

Output:

Animation26

Final Output



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

Similar Reads