Open In App

Food Delivery Application using MERN Stack

Last Updated : 05 Feb, 2024
Improve
Improve
Like Article
Like
Save
Share
Report

In the fast world where there is no time for people to cook food or go to restaurants to eat, Food Delivery applications are one of the best options for them. In this tutorial, you will learn how to create a simple food delivery app using MERN stack. Our 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

Screenshot-2567-01-28-at-083224-(2)

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 Application:

Step 1: creating the folder for the project

mkdir food-delivery-app
cd food-delivery-app

Step 2: Create a backend using the following commands

mkdir backend
cd backend
npm init -y

Step 3: Install the required dependencies.

npm i cors express mongoose nodemon

Project Structure(Backend):

sdfghtjyuio

Backend Folder

Backend Dependencies:

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

Example: Create server.js file and add the following code.

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
        useNewUrlParser: true,
        useUnifiedTopology: true,
    })
    .then(() => console.log("Connected to db"))
    .catch((err) => console.log("Error connecting to db", err));
 
const restaurantSchema = new mongoose.Schema({
    name: String,
    image: String,
    menu: [
        {
            name: String,
            price: Number,
            image: String,
        },
    ],
    rating: Number,
});
 
const Restaurant = mongoose.model("Restaurant", restaurantSchema);
 
// Define the PreviousOrder schema
const previousOrderSchema = new mongoose.Schema({
    orderId: { type: String, required: true },
    dateOfOrder: { type: Date, required: true },
    amount: { type: Number, required: true },
});
 
const PreviousOrder = mongoose.model("PreviousOrder", previousOrderSchema);
 
// Seed initial data
const seedData = [
    {
        name: "Italian Delight",
        menu: [
            {
                name: "Pasta Alfredo",
                price: 10,
                image:
            },
            {
                name: "Margherita Pizza",
                price: 15,
                image:
            },
            {
                name: "Chicken Parmesan",
                price: 20,
                image:
            },
        ],
        rating: 4.5,
    },
    {
        name: "Seafood Paradise",
        image:
        menu: [
            {
                name: "Grilled Salmon",
                price: 12,
                image:
            },
            {
                name: "Lobster Bisque",
                price: 18,
                image:
            },
            {
                name: "Shrimp Scampi",
                price: 25,
                image:
            },
        ],
        rating: 3.8,
    },
    {
        name: "Vegetarian Haven",
        menu: [
            {
                name: "Quinoa Salad",
                price: 8,
                image:
            },
            {
                name: "Eggplant Parmesan",
                price: 12,
                image:
            },
            {
                name: "Mushroom Risotto",
                price: 16,
                image:
            },
        ],
        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();
 
// Insert dummy data when the server starts
const insertDummyData = async () => {
    try {
        const existingOrders = await PreviousOrder.find();
 
        // Insert dummy data only if the database is empty
        if (existingOrders.length === 0) {
            const dummyOrders = [
                { orderId: "001", dateOfOrder: new Date(), amount: 30 },
                { orderId: "002", dateOfOrder: new Date(), amount: 45 },
                // Add more dummy orders as needed
            ];
 
            await PreviousOrder.insertMany(dummyOrders);
            console.log("Dummy data inserted successfully!");
        }
    } catch (error) {
        console.error("Error inserting dummy data:", error);
    }
};
insertDummyData();
 
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 });
    }
});
 
// Endpoint to retrieve previous orders
app.get("/previousOrders", async (req, res) => {
    try {
        const orders = await PreviousOrder.find();
        res.status(200).json(orders);
    } catch (error) {
        res.status(500).json({ error: "Internal server error" });
    }
});
// Endpoint to save order data
app.post("/previousOrders", async (req, res) => {
    try {
        const { orderId, dateOfOrder, amount } = req.body;
 
        console.log(orderId, dateOfOrder, amount);
 
        const newOrder = new PreviousOrder({
            orderId,
            dateOfOrder: new Date(dateOfOrder),
            amount,
        });
 
        await newOrder.save();
        res.status(201).json({ message: "Dummy order saved successfully!" });
    } catch (error) {
        res.status(500).json({ error: "Internal server error" });
    }
});
 
app.listen(PORT, () => {
    console.log(`Server is running on port ${PORT}`);
});


Step 4: To start the backend run the following command.

nodemon server.js

Step 5: Go to the root directory of the application and create the frontend application.

npx create-react-app client
cd client

Step 6: Install important dependencies: axios

npm i axios

Project Structure(Frontend):

dsfghj

Frontend Folder Structure.

Frontend dependencies:

 "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-router-dom": "^6.21.1",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
}

Example: Create the required files and add the following code.

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




// 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 Food Delivery App</h1>
                <Cart
                    style={{ position: "absolute", right: "20px", top: "20px" }}
                />
                <RestaurantList />
                {selectedRestaurant && <DishesMenu />}
            </div>
        </>
    );
};
 
export default App;


Javascript




//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,
                };
                setTotalPrice(totalPrice - cartItems[existingItemIndex].price);
            } else {
                // If the quantity is 1, remove the dish from the cart
                updatedCartItems.splice(existingItemIndex, 1);
                setTotalPrice(totalPrice - cartItems[existingItemIndex].price);
            }
 
            setCartItems(updatedCartItems);
        } 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 emptyCart = () => {
        setCartItems([]);
        setTotalPrice(0);
    };
    const value = {
        restaurants,
        selectedRestaurant,
        setSelectedRestaurant,
        handleAddItems,
        handleRemoveItems,
        totalPrice,
        emptyCart,
    };
 
    return (
        <RestaurantContext.Provider value={value}>
            {children}
        </RestaurantContext.Provider>
    );
};
 
export { RestaurantContext, RestaurantProvider };


Javascript




//Cart.js
 
import React, { useContext, useState } from "react";
import axios from "axios";
import { RestaurantContext } from "../contexts/RestaurantContext";
 
const Cart = () => {
    const { totalPrice, emptyCart } = useContext(RestaurantContext);
    const [isCheckingOut, setIsCheckingOut] = useState(false);
 
    const generateOrderId = () => {
        // Generate a unique order ID
        // (you can use a library like uuid for a more robust solution)
        return `${Math.floor(Math.random() * 1000)}`;
    };
 
    const handleCheckout = async () => {
        try {
            setIsCheckingOut(true);
 
            const orderId = generateOrderId();
 
            // Assuming you have a backend endpoint to handle the checkout
            const response = await axios.post(
                "http://localhost:5000/previousOrders",
                {
                    orderId,
                    dateOfOrder: new Date(),
                    amount: totalPrice,
                }
            );
 
            console.log(response.data);
            emptyCart();
        } catch (error) {
            console.error("Error during checkout:", error.message);
        } finally {
            setIsCheckingOut(false);
        }
    };
 
    return (
        <div className="cart-container">
            <h2>Cart</h2>
            <div className="cart-content">
                <span style={{ color: "brown" }}>Total Price: </span> $
                {totalPrice}
                <button onClick={handleCheckout} disabled={isCheckingOut}>
                    {isCheckingOut ? "Checking out..." : "Checkout"}
                </button>
            </div>
        </div>
    );
};
 
export default Cart;


Javascript




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




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


Javascript




//PreviousOrders.js
 
import React, { useState, useEffect } from 'react';
import axios from 'axios';
 
const PreviousOrders = ({ handleShow }) => {
    const [orders, setOrders] = useState([]);
 
    useEffect(() => {
        const fetchOrders = async () => {
            try {
                const response = await axios.get('http://localhost:5000/previousOrders');
                setOrders(response.data);
            } catch (error) {
                console.error('Error fetching orders:', error.message);
            }
        };
 
        fetchOrders();
    }, []);
 
    return (
        <div className="previous-orders-container">
            <h2>Your Previous Orders</h2>
            <button style={{ backgroundColor: "white", color: "red" }} onClick={handleShow}>Close</button>
            <ul className="orders-list">
                {orders.map(order => (
                    <li key={order.orderId} className="order-card">
                        <h3>Order #{order.orderId}</h3>
                        <div className="order-details">
                            <div>Items: 1</div>
                            <div>Total Amount: ${order.amount.toFixed(2)}</div>
                        </div>
                        <div>Ordered on: {new Date(order.dateOfOrder).toLocaleDateString()}</div>
                    </li>
                ))}
            </ul>
        </div>
    );
};
 
export default PreviousOrders;


Javascript




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




//RestaurantList.js
 
import React, { useContext, useState, useEffect } from 'react';
import RestaurantCard from './RestaurantCard';
import { RestaurantContext } from '../contexts/RestaurantContext';
import PreviousOrders from './PreviousOders';
 
 
const RestaurantList = () => {
    const { restaurants, setSelectedRestaurant } = useContext(RestaurantContext);
    const [filteredRestaurants, setFilteredRestaurants] = useState([...restaurants]);
    const [ratingFilter, setRatingFilter] = useState('');
    const [searchTerm, setSearchTerm] = useState('');
    const [showOrder, setShowOrder] = useState(false)
 
    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);
    };
    const handleShow = () => {
        setShowOrder(!showOrder)
    }
    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"
                />
                <p id='pre-orders' onClick={handleShow}>
                    Previous Orders
                </p>
            </div>
            <div className="restaurant-card-container">
                {filteredRestaurants.map((restaurant) => (
                    <RestaurantCard
                        key={restaurant._id}
                        restaurant={restaurant}
                        onClick={() => handleRestaurantClick(restaurant._id)}
                    />
                ))}
            </div>
            {showOrder && <PreviousOrders handleShow={handleShow} />}
 
        </div>
    );
};
 
export default RestaurantList;


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: #fc0671;
    color: white;
    padding: 10px;
}
 
.filter-container {
    display: flex;
    justify-content: center;
    align-items: center;
    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 #fc0671;
    border-radius: 10px;
    height: fit-content;
    padding: 5px 10px;
    background-color: #fff;
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
    overflow-y: auto;
    z-index: 1000;
}
 
.cart-container button {
    background-color: #fc0671;
    color: white;
    border: none;
    border-radius: 10px;
}
 
h2 {
    margin-bottom: 10px;
    margin-left: 20px;
}
 
.cart-content {
    padding: 16px;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: flex-start;
}
 
 
 
#pre-orders {
    background-color: #fc0671;
    color: aliceblue;
    font-size: 20px;
    padding: 5px 10px;
    border-radius: 10px;
    cursor: pointer;
}
 
 
 
/* Previous orders  */
/* PreviousOrders.css */
 
.previous-orders-container {
    max-width: 600px;
    margin: 20px auto;
    padding: 20px;
    position: absolute;
    background-color: #fc0671;
    color: white;
    border-radius: 10px;
}
 
.orders-list {
    list-style: none;
    padding: 0;
}
 
.order-card {
    background-color: #fff;
    border: 1px solid #ddd;
    border-radius: 8px;
    padding: 15px;
    margin-bottom: 15px;
    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
    transition: transform 0.3s ease-in-out;
}
 
.order-card:hover {
    transform: scale(1.02);
}
 
.order-card h3 {
    color: #333;
    margin-bottom: 10px;
}
 
.order-details {
    display: flex;
    justify-content: space-between;
    font-size: 14px;
    color: #666;
}


Step 7: To start the frontend run the following command.

npm start

Output:

Untitled-design-(31)

Final Output



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

Similar Reads