Tutorial on developing Yelp Clone
habeebahmed
Posted on April 6, 2023
Disclaimer: The code was written with the help of ChatGPT
Features that will be covered in this blog are:
- Login and Signup.
- View restaurant ratings.
- Provide feedback and ratings to restaurants.
- Upload photos.
Tech stack: React JS, Node JS, MongoDB, AWS and EKS
We shall be going through following steps:
1. Set up the development environment:
- Install Node.js and npm (Node Package Manager) if you haven't already.
- Install MongoDB locally or set up a cloud instance using MongoDB Atlas.
- Install the AWS CLI and configure your AWS account credentials.
2. Initialize the project:
- Create a new directory for your project and navigate to it in the terminal.
- Run npm init to create a package.json file for your project.
- Run npm install react react-dom to install React and ReactDOM.
- Run npm install express mongoose to install Express.js (Node.js web framework) and Mongoose (MongoDB object modeling tool).
3. Set up the backend:
- Create a new directory named server in your project root.
- Inside the server directory, create a new file named app.js. - This file will contain the server logic.
- Set up the Express.js server, connect to the MongoDB database using Mongoose, and define the required routes (e.g., for user authentication, fetching restaurant ratings, and adding new ratings and photos).
4. Set up the frontend:
- Create a new directory named client in your project root.
- Inside the client directory, create a new directory named src. This directory will contain your React components and application logic.
- Create a new file named index.js in the src directory. This file will render the root React component and mount it to the DOM.
- Create React components for the login/signup page, the restaurant ratings display, and the rating/review submission form.
5. Implement user authentication:
- In the backend, set up user registration and login routes using Passport.js and JSON Web Tokens (JWT) for authentication.
- In the frontend, create a form for users to log in or sign up, and store the JWT in the browser's local storage.
6. Implement the restaurant ratings feature:
- Create a MongoDB schema for restaurants, which includes fields such as the name, location, average rating, and user reviews.
- In the backend, create API routes to fetch and submit restaurant ratings.
- In the frontend, create components to display the restaurant ratings and submit new ratings after a user logs in.
7. Implement the photo upload feature:
- Use AWS S3 to store user-uploaded photos.
- In the backend, create an API route to handle file uploads, using the multer middleware to handle file uploads in Express.js, and the aws-sdk to interact with AWS S3.
- In the frontend, add a file input field to the review submission form, and modify the form submission logic to include the photo upload.
8. Set up multi-environment configurations:
- Create separate configuration files (e.g., .env.development, .env.production) to store environment-specific variables, such as API endpoints and MongoDB connection strings.
- Use the dotenv package to load the appropriate configuration file based on the current environment.
9. Deploy the application:
- Set up AWS ECS (Elastic Container Service) and EKS (Elastic Kubernetes Service) to deploy your application on a container orchestration platform.
- Create a Dockerfile for your application, and build a Docker image.
- Push the Docker image to Amazon ECR (Elastic Container Registry).
- Create a Kubernetes deployment and service manifest for your application.
- Use kubectl to apply the manifest and deploy your application on EKS.
Project Structure:
backend/
|- models/
| |- User.js
| |- Restaurant.js
| └- Review.js
|- routes/
| |- auth.js
| |- restaurant.js
| └- review.js
|- middleware/
| |- auth.js
| └- errorHandler.js
|- utils/
| └- upload.js
|- .env
|- app.js
└- package.json
frontend/
|- public/
| └- index.html
|- src/
| |- components/
| | |- Navbar.js
| | |- RestaurantCard.js
| | |- ReviewForm.js
| | └- PhotoUploader.js
| |- contexts/
| | └- AuthContext.js
| |- pages/
| | |- HomePage.js
| | |- LoginPage.js
| | |- SignupPage.js
| | |- UserProfile.js
| | └- RestaurantDetails.js
| |- utils/
| | └- api.js
| |- App.js
| |- Router.js
| └- index.js
└- package.json
Backend Setup
- package.json
{
"name": "backend",
"version": "1.0.0",
"description": "Backend for the Yelp-like web application",
"main": "app.js",
"scripts": {
"start": "node app.js",
"dev": "nodemon app.js"
},
"repository": {
"type": "git",
"url": "your-repository-url"
},
"author": "Your Name",
"license": "MIT",
"dependencies": {
"bcrypt": "^5.0.1",
"cors": "^2.8.5",
"dotenv": "^10.0.0",
"express": "^4.17.1",
"jsonwebtoken": "^8.5.1",
"mongoose": "^6.1.2",
"multer": "^1.4.4"
},
"devDependencies": {
"nodemon": "^2.0.15"
}
}
- .env
MONGO_URI=<your_mongodb_connection_uri>
JWT_SECRET=<your_jwt_secret_key>
PORT=5000
Replace with your MongoDB connection URI and with a secure and unique secret key for your JSON Web Token (JWT) authentication.
- server/app.js(Express.js server setup and MongoDB connection)
const express = require('express');
const mongoose = require('mongoose');
const cors = require('cors');
const dotenv = require('dotenv');
const errorHandler = require('./middleware/errorHandler');
// Load environment variables
dotenv.config();
const app = express();
// Middleware
app.use(cors());
app.use(express.json());
// Connect to MongoDB
mongoose.connect(process.env.MONGODB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
})
.then(() => console.log('Connected to MongoDB'))
.catch((err) => console.error('Failed to connect to MongoDB', err));
// Import and use API routes
const authRoutes = require('./routes/auth');
const restaurantRoutes = require('./routes/restaurant');
app.use('/api/auth', authRoutes);
app.use('/api/restaurants', restaurantRoutes);
// Add the custom error handling middleware
app.use(errorHandler);
// Start the server
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
- Models #### Backend - server/models/User.js (User schema for authentication)
const mongoose = require('mongoose');
const bcrypt = require('bcrypt');
const userSchema = new mongoose.Schema({
email: {
type: String,
required: true,
unique: true,
},
password: {
type: String,
required: true,
},
});
// Hash the password before saving the user
userSchema.pre('save', async function (next) {
if (this.isModified('password')) {
this.password = await bcrypt.hash(this.password, 10);
}
next();
});
// Compare password hashes for authentication
userSchema.methods.comparePassword = function (candidatePassword) {
return bcrypt.compare(candidatePassword, this.password);
};
module.exports = mongoose.model('User', userSchema);
Backend - server/models/Restaurants.js
const mongoose = require('mongoose');
const RestaurantSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
address: {
type: String,
required: true
},
rating: {
type: Number,
default: 0
},
reviews: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Review'
}
],
createdAt: {
type: Date,
default: Date.now
}
});
module.exports = mongoose.model('Restaurant', RestaurantSchema);
Backend - server/models/Review.js
const mongoose = require('mongoose');
const ReviewSchema = new mongoose.Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
},
restaurant: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Restaurant',
required: true
},
text: {
type: String,
required: true
},
rating: {
type: Number,
required: true
},
photos: [
{
url: {
type: String,
required: true
}
}
],
createdAt: {
type: Date,
default: Date.now
}
});
module.exports = mongoose.model('Review', ReviewSchema);
- Routes #### Backend - server/routes/auth.js (User authentication routes)
const express = require('express');
const jwt = require('jsonwebtoken');
const User = require('../models/User');
const router = express.Router();
// POST /api/auth/signup - register a new user
router.post('/signup', async (req, res) => {
const { email, password } = req.body;
try {
const user = new User({ email, password });
await user.save();
res.status(201).json({ message: 'User created' });
} catch (error) {
res.status(500).json({ message: 'Error creating user' });
}
});
// POST /api/auth/login - log in a user and return a JWT
router.post('/login', async (req, res) => {
const { email, password } = req.body;
try {
const user = await User.findOne({ email });
if (!user || !(await user.comparePassword(password))) {
return res.status(401).json({ message: 'Invalid email or password' });
}
const token = jwt.sign({ userId: user._id }, process.env.JWT_SECRET, { expiresIn: '1d' });
res.status(200).json({ token });
} catch (error) {
res.status(500).json({ message: 'Error logging in user' });
}
});
module.exports = router;
Backend - server/routes/restaurant.js (API route to fetch a restaurant's details and its reviews)
const express = require('express');
const Restaurant = require('../models/Restaurant');
const Review = require('../models/Review');
const auth = require('../middleware/auth');
const router = express.Router();
// GET /api/restaurants/:id - fetch a restaurant's details and its reviews
router.get('/:id', async (req, res) => {
try {
const restaurant = await Restaurant.findById(req.params.id);
if (!restaurant) {
return res.status(404).json({ message: 'Restaurant not found' });
}
const reviews = await Review.find({ restaurant: req.params.id }).populate('user', 'email');
res.json({ restaurant, reviews });
} catch (error) {
res.status(500).json({ message: 'Error fetching restaurant details' });
}
});
router.get('/', async (req, res) => {
try {
const restaurants = await Restaurant.find();
res.json(restaurants);
} catch (error) {
res.status(500).json({ message: 'Error fetching restaurants' });
}
});
// POST /api/restaurants - create a new restaurant (requires authentication)
router.post('/', auth, async (req, res) => {
const { name, description } = req.body;
try {
const restaurant = new Restaurant({
name,
description,
});
await restaurant.save();
res.status(201).json({ message: 'Restaurant created' });
} catch (error) {
res.status(500).json({ message: 'Error creating restaurant' });
}
});
module.exports = router;
Backend - server/routes/review.js (API route to submit a new review)
const express = require('express');
const auth = require('../middleware/auth');
const Review = require('../models/Review');
const router = express.Router();
// POST /api/reviews - submit a new review (requires authentication)
router.post('/', auth, async (req, res) => {
const { restaurantId, rating, comment } = req.body;
try {
const review = new Review({
user: req.userId,
restaurant: restaurantId,
rating,
comment,
});
await review.save();
res.status(201).json({ message: 'Review submitted' });
} catch (error) {
res.status(500).json({ message: 'Error submitting review' });
}
});
module.exports = router;
Backend - server/middleware/auth.js (JWT authentication middleware)
const jwt = require('jsonwebtoken');
// Middleware to verify the JWT and extract the user ID
const auth = (req, res, next) => {
const token = req.header('Authorization');
if (!token) {
return res.status(401).json({ message: 'No token provided' });
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.userId = decoded.userId;
next();
} catch (error) {
res.status(401).json({ message: 'Invalid token' });
}
};
module.exports = auth;
Backend - server/middleware/errorHandler.js (JWT authentication middleware)
const errorHandler = (err, req, res, next) => {
// Log the error for debugging purposes
console.error(err.stack);
// Set default error properties
let statusCode = 500;
let message = 'An unexpected error occurred';
// Customize the error response based on the error type
if (err.name === 'ValidationError') {
statusCode = 400;
message = err.message;
} else if (err.name === 'CastError') {
statusCode = 400;
message = 'Invalid ID format';
} else if (err.name === 'UnauthorizedError') {
statusCode = 401;
message = 'Unauthorized';
}
// Send the error response
res.status(statusCode).json({
error: {
message: message,
status: statusCode,
details: process.env.NODE_ENV === 'development' ? err.stack : undefined
}
});
};
module.exports = errorHandler;
Backend - server/utils/upload.js (JWT authentication middleware)
const multer = require('multer');
const path = require('path');
// Configure storage for file uploads
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, 'uploads/');
},
filename: function (req, file, cb) {
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1e9);
cb(null, uniqueSuffix + path.extname(file.originalname));
}
});
// File upload filter to check for valid file types
const fileFilter = (req, file, cb) => {
const filetypes = /jpeg|jpg|png/;
const mimetype = filetypes.test(file.mimetype);
const extname = filetypes.test(path.extname(file.originalname).toLowerCase());
if (mimetype && extname) {
return cb(null, true);
}
cb(new Error('Only jpeg, jpg, and png images are allowed.'));
};
// Initialize multer with the defined storage and filter options
const upload = multer({
storage: storage,
fileFilter: fileFilter,
limits: {
fileSize: 5 * 1024 * 1024 // Limit file size to 5MB
}
});
module.exports = upload;
Frontend setup
Frontend - public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="A Yelp-like web application" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>Yelp-like Web Application</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>
Frontend - src/components/Navbar.js
import React from 'react';
import { Link } from 'react-router-dom';
const Navbar = () => {
return (
<nav className="navbar">
<div className="container">
<Link to="/" className="navbar-brand">
Yelp-like App
</Link>
<ul className="navbar-nav">
<li className="nav-item">
<Link to="/restaurants" className="nav-link">
Restaurants
</Link>
</li>
<li className="nav-item">
<Link to="/login" className="nav-link">
Login
</Link>
</li>
<li className="nav-item">
<Link to="/signup" className="nav-link">
Sign Up
</Link>
</li>
</ul>
</div>
</nav>
);
};
export default Navbar;
Frontend - src/components/RestaurantCard.js
import React from 'react';
import { Link } from 'react-router-dom';
const RestaurantCard = ({ restaurant }) => {
return (
<div className="restaurant-card">
<img src={restaurant.image} alt={restaurant.name} className="restaurant-image" />
<div className="restaurant-details">
<h3 className="restaurant-name">
<Link to={`/restaurants/${restaurant._id}`}>{restaurant.name}</Link>
</h3>
<p className="restaurant-address">{restaurant.address}</p>
<div className="restaurant-rating">
<span>{restaurant.rating.toFixed(1)}</span>
<span className="rating-stars">{/* Add star rating component here */}</span>
</div>
<p className="restaurant-review-count">{restaurant.reviews.length} reviews</p>
</div>
</div>
);
};
export default RestaurantCard;
Frontend - src/components/ReviewForm.js
import React, { useState } from 'react';
const ReviewForm = ({ onSubmit }) => {
const [text, setText] = useState('');
const [rating, setRating] = useState(1);
const handleSubmit = (e) => {
e.preventDefault();
onSubmit({ text, rating });
setText('');
setRating(1);
};
return (
<form className="review-form" onSubmit={handleSubmit}>
<div className="form-group">
<label htmlFor="text">Review:</label>
<textarea
id="text"
name="text"
value={text}
onChange={(e) => setText(e.target.value)}
required
></textarea>
</div>
<div className="form-group">
<label htmlFor="rating">Rating:</label>
<select
id="rating"
name="rating"
value={rating}
onChange={(e) => setRating(parseInt(e.target.value))}
>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
</select>
</div>
<button type="submit" className="submit-review">
Submit Review
</button>
</form>
);
};
export default ReviewForm;
Frontend - src/components/PhotoUploader.js
import React, { useState } from 'react';
const PhotoUploader = ({ onUpload }) => {
const [selectedFile, setSelectedFile] = useState(null);
const handleFileChange = (e) => {
setSelectedFile(e.target.files[0]);
};
const handleSubmit = async (e) => {
e.preventDefault();
if (!selectedFile) {
alert('Please select a file to upload.');
return;
}
const formData = new FormData();
formData.append('image', selectedFile);
// Call the onUpload function with the formData
onUpload(formData);
// Reset the file input
setSelectedFile(null);
e.target.reset();
};
return (
<form className="photo-uploader" onSubmit={handleSubmit}>
<div className="form-group">
<input type="file" accept="image/*" onChange={handleFileChange} />
</div>
<button type="submit" className="upload-button">
Upload Photo
</button>
</form>
);
};
export default PhotoUploader;
Frontend - src/contexts/AuthContext.js
import React, { createContext, useState, useEffect } from 'react';
export const AuthContext = createContext();
const AuthProvider = ({ children }) => {
const [currentUser, setCurrentUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
// Simulate an asynchronous function call to check for an authenticated user
const fetchCurrentUser = async () => {
// Replace this with a call to your backend to fetch the current user
const user = await new Promise((resolve) => {
setTimeout(() => resolve(null), 1000);
});
setCurrentUser(user);
setLoading(false);
};
fetchCurrentUser();
}, []);
const login = async (email, password) => {
// Perform authentication and set the currentUser
// Replace this with a call to your backend to login the user
};
const signup = async (email, password) => {
// Register a new user and set the currentUser
// Replace this with a call to your backend to register the user
};
const logout = async () => {
// Log out the user and reset the currentUser
// Replace this with a call to your backend to log out the user
setCurrentUser(null);
};
const value = {
currentUser,
loading,
login,
signup,
logout,
};
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
};
export default AuthProvider;
Frontend - src/pages/HomePage.js
import React, { useEffect, useState } from 'react';
const HomePage = () => {
const [restaurants, setRestaurants] = useState([]);
useEffect(() => {
fetch('/api/restaurants')
.then((response) => response.json())
.then((data) => setRestaurants(data))
.catch((error) => console.error('Error fetching restaurant ratings', error));
}, []);
return (
<div>
<h1>Restaurant Ratings</h1>
<ul>
{restaurants.map((restaurant) => (
<li key={restaurant._id}>
<h2>{restaurant.name}</h2>
<p>Location: {restaurant.location}</p>
<p>Rating: {restaurant.rating}</p>
</li>
))}
</ul>
</div>
);
};
export default HomePage;
Frontend - src/pages/LoginPage.js
import React, { useState } from 'react';
const LoginPage = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const handleSubmit = async (e) => {
e.preventDefault();
try {
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
});
if (!response.ok) {
throw new Error('Invalid email or password');
}
const data = await response.json();
localStorage.setItem('token', data.token);
alert('Logged in successfully');
} catch (error) {
alert(error.message);
}
};
return (
<div>
<h1>Login</h1>
<form onSubmit={handleSubmit}>
<label>
Email:
<input type="email" value={email} onChange={(e) => setEmail(e.target.value)} />
</label>
<label>
Password:
<input type="password" value={password} onChange={(e) => setPassword(e.target.value)} />
</label>
<button type="submit">Login</button>
</form>
</div>
);
};
export default LoginPage;
Frontend - src/pages/SignupPage.js
import React, { useState } from 'react';
const SignupPage = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const handleSubmit = async (e) => {
e.preventDefault();
try {
const response = await fetch('/api/auth/signup', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
});
if (!response.ok) {
throw new Error('Error creating user');
}
alert('User created successfully');
} catch (error) {
alert(error.message);
}
};
return (
<div>
<h1>Sign Up</h1>
<form onSubmit={handleSubmit}>
<label>
Email:
<input type="email" value={email} onChange={(e) => setEmail(e.target.value)} />
</label>
<label>
Password:
<input type="password" value={password} onChange={(e) => setPassword(e.target.value)} />
</label>
<button type="submit">Sign Up</button>
</form>
</div>
);
};
export default SignupPage;
Frontend - src/pages/UserProfile.js
import React, { useContext } from 'react';
import { AuthContext } from './AuthContext';
const UserProfile = () => {
const { currentUser, logout } = useContext(AuthContext);
if (!currentUser) {
return <p>Please log in to view your profile.</p>;
}
return (
<div className="user-profile">
<h2>User Profile</h2>
<p>Email: {currentUser.email}</p>
<p>Name: {currentUser.name}</p>
{/* Add more user details here */}
<button onClick={logout}>Log Out</button>
</div>
);
};
export default UserProfile;
Frontend - src/pages/RestaurantDetails.js
import React, { useState, useEffect } from 'react';
import ReviewForm from '../components/ReviewForm';
const RestaurantDetails = ({ match }) => {
const [restaurant, setRestaurant] = useState(null);
const [reviews, setReviews] = useState([]);
useEffect(() => {
const fetchRestaurant = async () => {
const response = await fetch(`/api/restaurants/${match.params.id}`);
const data = await response.json();
setRestaurant(data.restaurant);
setReviews(data.reviews);
};
fetchRestaurant();
}, [match.params.id]);
if (!restaurant) {
return <div>Loading...</div>;
}
return (
<div>
<h1>{restaurant.name}</h1>
<p>{restaurant.description}</p>
<h2>Reviews</h2>
{reviews.map((review) => (
<div key={review._id}>
<p>
<strong>{review.user.email}</strong> - {review.rating} stars
</p>
<p>{review.comment}</p>
</div>
))}
<ReviewForm restaurantId={restaurant._id} />
</div>
);
};
export default RestaurantDetails;
Frontend - src/utils/api.js
const BASE_URL = 'http://localhost:5000/api';
const headers = {
'Content-Type': 'application/json',
};
const handleResponse = async (response) => {
if (!response.ok) {
const error = await response.json();
throw new Error(error.message || 'Something went wrong');
}
return response.json();
};
const api = {
get: async (endpoint) => {
const response = await fetch(`${BASE_URL}${endpoint}`, {
method: 'GET',
headers,
});
return handleResponse(response);
},
post: async (endpoint, data) => {
const response = await fetch(`${BASE_URL}${endpoint}`, {
method: 'POST',
headers,
body: JSON.stringify(data),
});
return handleResponse(response);
},
put: async (endpoint, data) => {
const response = await fetch(`${BASE_URL}${endpoint}`, {
method: 'PUT',
headers,
body: JSON.stringify(data),
});
return handleResponse(response);
},
delete: async (endpoint) => {
const response = await fetch(`${BASE_URL}${endpoint}`, {
method: 'DELETE',
headers,
});
return handleResponse(response);
},
// Add any additional methods you may need, such as PATCH or custom request configurations
};
export default api;
Frontend - src/App.js
import React from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import './App.css';
import Navbar from './components/Navbar';
import HomePage from './components/HomePage';
import RestaurantPage from './components/RestaurantPage';
import UserProfile from './components/UserProfile';
import Login from './components/Login';
import Signup from './components/Signup';
function App() {
return (
<Router>
<div className="App">
<Navbar />
<div className="container">
<Switch>
<Route path="/" component={HomePage} exact />
<Route path="/restaurants/:id" component={RestaurantPage} />
<Route path="/profile" component={UserProfile} />
<Route path="/login" component={Login} />
<Route path="/signup" component={Signup} />
{/* Add additional routes as needed */}
</Switch>
</div>
</div>
</Router>
);
}
export default App;
Frontend - src/Router.js
import React from 'react';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import HomePage from './pages/HomePage';
import LoginPage from './pages/LoginPage';
import SignupPage from './pages/SignupPage';
const Router = () => {
return (
<BrowserRouter>
<Switch>
<Route path="/" exact component={HomePage} />
<Route path="/login" component={LoginPage} />
<Route path="/signup" component={SignupPage} />
</Switch>
</BrowserRouter>
);
};
export default Router;
Frontend - src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import AuthProvider from './AuthContext';
import reportWebVitals from './reportWebVitals';
ReactDOM.render(
<React.StrictMode>
<AuthProvider>
<App />
</AuthProvider>
</React.StrictMode>,
document.getElementById('root')
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
frontend/package.json
{
"name": "frontend",
"version": "1.0.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.16.1",
"@testing-library/react": "^12.1.3",
"@testing-library/user-event": "^13.5.0",
"axios": "^0.26.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-router-dom": "^6.3.6",
"react-scripts": "5.0.0",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
Deployment strategy:
1. Set up an Amazon EKS cluster:
- Install and configure the eksctl command line tool.
- Create an EKS cluster using the eksctl create cluster command.
- Configure kubectl to communicate with your cluster.
2. Create Kubernetes manifests for your frontend and backend services. You will need to create Deployment and Service manifests for both frontend and backend. Make sure to update the image names in the manifests with the ones you pushed to Docker Hub.
3. Apply the Kubernetes manifests using kubectl apply -f <manifest-file>.
- Access your application using the Kubernetes LoadBalancer service's external IP.
- Set up an Amazon EKS cluster:
- Install and configure the aws CLI and eksctl command-line tool.
- Create a new EKS cluster using the eksctl create cluster command. Refer to the official documentation for more details. Configure kubectl to communicate with your cluster: Update the kubeconfig for your cluster using the following command:
aws eks update-kubeconfig --region your-region --name your-cluster-name
- Create Kubernetes manifests for your frontend and backend services: -- Create a k8s directory in your project root and store the following manifests:
-- Frontend Deployment: frontend-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
spec:
replicas: 1
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: frontend
spec:
containers:
- name: frontend
image: your-dockerhub-username/frontend
ports:
- containerPort: 80
-- Frontend Service: frontend-service.yaml
apiVersion: v1
kind: Service
metadata:
name: frontend
spec:
selector:
app: frontend
ports:
- protocol: TCP
port: 80
targetPort: 80
type: LoadBalancer
-- Backend Deployment: backend-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend
spec:
replicas: 1
selector:
matchLabels:
app: backend
template:
metadata:
labels:
app: backend
spec:
containers:
- name: backend
image: your-dockerhub-username/backend
ports:
- containerPort: 5000
env:
- name: MONGO_URI
value: your-mongodb-atlas-uri
- name: JWT_SECRET
value: your-jwt-secret
-- Backend Service: backend-service.yaml
apiVersion: v1
kind: Service
metadata:
name: backend
spec:
selector:
app: backend
ports:
- protocol: TCP
port: 5000
targetPort: 5000
type: LoadBalancer
- Apply the Kubernetes manifests: Deploy your application using the following commands:
kubectl apply -f k8s/frontend-deployment.yaml
kubectl apply -f k8s/frontend-service.yaml
kubectl apply -f k8s/backend-deployment.yaml
kubectl apply -f k8s/backend-service.yaml
- Access your application: -- Get the external IP addresses for the frontend and backend services:
kubectl get svc
💖 💪 🙅 🚩
habeebahmed
Posted on April 6, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.