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.

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):
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.
//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",
image: "https://media.geeksforgeeks.org/wp-content/uploads/20240110004602/pexels-chan-walrus-958545-(1).jpg",
menu: [
{
name: "Pasta Alfredo",
price: 10,
image: "https://media.geeksforgeeks.org/wp-content/uploads/20240110004646/file.jpg",
},
{
name: "Margherita Pizza",
price: 15,
image: "https://media.geeksforgeeks.org/wp-content/uploads/20240110004646/file.jpg",
},
{
name: "Chicken Parmesan",
price: 20,
image: "https://media.geeksforgeeks.org/wp-content/uploads/20240110004646/file.jpg",
},
],
rating: 4.5,
},
{
name: "Seafood Paradise",
image: "https://media.geeksforgeeks.org/wp-content/uploads/20240110004602/pexels-chan-walrus-958545-(1).jpg",
menu: [
{
name: "Grilled Salmon",
price: 12,
image: "https://media.geeksforgeeks.org/wp-content/uploads/20240110004602/pexels-chan-walrus-958545-(1).jpg",
},
{
name: "Lobster Bisque",
price: 18,
image: "https://media.geeksforgeeks.org/wp-content/uploads/20240110004602/pexels-chan-walrus-958545-(1).jpg",
},
{
name: "Shrimp Scampi",
price: 25,
image: "https://media.geeksforgeeks.org/wp-content/uploads/20240110004602/pexels-chan-walrus-958545-(1).jpg",
},
],
rating: 3.8,
},
{
name: "Vegetarian Haven",
image: "https://media.geeksforgeeks.org/wp-content/uploads/20240110004602/pexels-chan-walrus-958545-(1).jpg",
menu: [
{
name: "Quinoa Salad",
price: 8,
image: "https://media.geeksforgeeks.org/wp-content/uploads/20240110004602/pexels-chan-walrus-958545-(1).jpg",
},
{
name: "Eggplant Parmesan",
price: 12,
image: "https://media.geeksforgeeks.org/wp-content/uploads/20240110004602/pexels-chan-walrus-958545-(1).jpg",
},
{
name: "Mushroom Risotto",
price: 16,
image: "https://media.geeksforgeeks.org/wp-content/uploads/20240110004602/pexels-chan-walrus-958545-(1).jpg",
},
],
rating: 4.2,
},
{
name: "Sizzling Steakhouse",
image: "https://media.geeksforgeeks.org/wp-content/uploads/20240110004602/pexels-chan-walrus-958545-(1).jpg",
menu: [
{
name: "Filet Mignon",
price: 22,
image: "https://media.geeksforgeeks.org/wp-content/uploads/20240110004602/pexels-chan-walrus-958545-(1).jpg",
},
{
name: "New York Strip",
price: 18,
image: "https://media.geeksforgeeks.org/wp-content/uploads/20240110004602/pexels-chan-walrus-958545-(1).jpg",
},
{
name: "Ribeye Steak",
price: 25,
image: "https://media.geeksforgeeks.org/wp-content/uploads/20240110004602/pexels-chan-walrus-958545-(1).jpg",
},
],
rating: 4.7,
},
{
name: "Asian Fusion",
image: "https://media.geeksforgeeks.org/wp-content/uploads/20240110004602/pexels-chan-walrus-958545-(1).jpg",
menu: [
{
name: "Sushi Platter",
price: 20,
image: "https://media.geeksforgeeks.org/wp-content/uploads/20240110004602/pexels-chan-walrus-958545-(1).jpg",
},
{
name: "Pad Thai",
price: 15,
image: "https://media.geeksforgeeks.org/wp-content/uploads/20240110004602/pexels-chan-walrus-958545-(1).jpg",
},
{
name: "Mongolian Beef",
price: 18,
image: "https://media.geeksforgeeks.org/wp-content/uploads/20240110004602/pexels-chan-walrus-958545-(1).jpg",
},
],
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):

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.
/* 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;
}
// 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;
//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();
//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 };
//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;
//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;
//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;
//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;
//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;
Step to Run the code by typing the following command
- Start the backend:
node server.js
- Start the frontend:
npm start
Output: