Making a restaurant food delivery app :Tech used and the entire process !
Sep 10, 2024
14 min read
1
27
0
Uddit,10-September-2024 ,Tuesday
Let's break down the front end for both the client side (the food ordering app) and the restaurant side (the admin panel) , followed by how these front ends connect to the backend using tools like API calls , JWT authentication , and WebSocket for real-time communication.
Client Side Frontend (Food Ordering App)
The client-side frontend is built using React Native .It allows customers to:
- Browse food items and restaurants
- Place orders
- Track order status and delivery location
- Login/Signup
Client-Side Breakdown :
1. Login/Signup Page :
- Users enter their credentials, and these details are sent to the backend using an HTTP POST request.
- After successful authentication, the backend sends a JWT token to the frontend, which is stored on the client (either in `AsyncStorage` or secure storage).
```javascript
const loginUser = async () => {
const response = await fetch('http://backend-url/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
});
const data = await response.json();
if (data.token) {
// Save token securely on the client
AsyncStorage.setItem('token', data.token);
}
};
```
2. Food Menu :
- Fetch the available restaurants and food items from the backend using API calls . For example, you can make a GET request to `/api/restaurants` to get the list of restaurants.
- This data is displayed using React Native components like `FlatList`.
```javascript
const fetchMenu = async () => {
const token = await AsyncStorage.getItem('token');
const response = await fetch('http://backend-url/api/menu', {
headers: { 'Authorization': `Bearer ${token}` },
});
const menu = await response.json();
setMenuItems(menu);
};
```
3. Placing Orders :
- When the user places an order, an HTTP POST request is made to the backend, which contains the food item details and the JWT token to authenticate the user.
```javascript
const placeOrder = async (order) => {
const token = await AsyncStorage.getItem('token');
const response = await fetch('http://backend-url/api/order', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(order),
});
const data = await response.json();
// Handle response (e.g., show order confirmation)
};
```
4. Real-Time Order Tracking (WebSocket) :
- Once the order is placed, the app subscribes to real-time updates from the WebSocket server for delivery status and location.
- As the delivery partner updates their location, the WebSocket will push those updates to the client.
```javascript
useEffect(() => {
const socket = io('http://backend-url');
socket.on('deliveryLocation', (locationData) => {
// Update map with new location data
});
}, []);
```
Restaurant Side Frontend (Admin Panel)
The admin panel is built using React.js (for web). It allows the restaurant to:
- View incoming orders
- Update the order status (e.g., "Preparing", "Out for Delivery")
- Track order details
Restaurant-Side Breakdown :
1. Login Page :
- Similar to the client app, the admin panel uses an HTTP POST request to the backend for login, receiving a JWT token .
```javascript
const loginAdmin = async () => {
const response = await fetch('http://backend-url/admin/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
});
const data = await response.json();
if (data.token) {
// Save token securely in localStorage
localStorage.setItem('adminToken', data.token);
}
};
```
2. Order Management :
- Admins can fetch the list of incoming orders from the backend via an HTTP GET request, and display the orders using components like `Table` or `List`.
- The backend API verifies the JWT token to ensure the admin is authorized to view orders.
```javascript
const fetchOrders = async () => {
const token = localStorage.getItem('adminToken');
const response = await fetch('http://backend-url/admin/orders', {
headers: { 'Authorization': `Bearer ${token}` },
});
const orders = await response.json();
setOrders(orders);
};
```
3. Updating Order Status :
- Admins can update the order status (e.g., “Preparing”, “Out for Delivery”) using an HTTP PUT or POST request to the backend. This action updates the order in the database and notifies the client via WebSocket.
```javascript
const updateOrderStatus = async (orderId, status) => {
const token = localStorage.getItem('adminToken');
await fetch(`http://backend-url/admin/order/${orderId}`, {
method: 'PUT',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ status }),
});
// Inform the client of the status change using WebSocket
socket.emit('orderStatus', { orderId, status });
};
```
Connecting Frontend to Backend
1. Authentication (JWT Tokens) :
Both the client app and the admin panel use JWT tokens for authentication. Once users or admins log in, the backend issues a JWT token, which is used in subsequent API requests to authenticate the user.
- On the frontend (client/admin side), the token is stored securely (`AsyncStorage` in React Native, `localStorage` in React).
- Every time the frontend makes a request to a protected route (like placing an order or fetching order data), the token is passed in the `Authorization` header of the request.
```http
Authorization: Bearer <JWT_TOKEN>
```
The backend verifies the token using the secret key and ensures that only authenticated users can access the route.
2. HTTP Requests (API Calls) :
The client and admin panel communicate with the backend using HTTP requests to the REST API . Common methods include:
- GET : For retrieving data (e.g., fetching the food menu or orders).
- POST : For sending data (e.g., placing an order or logging in).
- PUT : For updating data (e.g., changing order status).
3. Real-Time Updates (WebSockets) :
To handle real-time updates, WebSocket connections are established between the frontend and the backend.
- Client App : The client listens for real-time updates about their order status and the delivery partner’s location.
- Admin Panel : The restaurant can send real-time updates to the client about their order status (e.g., “Out for Delivery”).
```javascript
// Client Side (React Native)
useEffect(() => {
const socket = io('http://backend-url');
socket.on('orderUpdate', (update) => {
// Display order status update to the user
});
}, []);
// Admin Panel (React.js)
const updateOrderStatus = (orderId, status) => {
socket.emit('orderStatus', { orderId, status });
};
```
Summary :
- Frontend (Client Side) : Built with React Native, handles user login/signup, menu browsing, order placing, and real-time order tracking.
- Frontend (Admin Side) : Built with React.js, allows the restaurant to view and manage orders and send updates.
- Backend : Built with Node.js, Express, and MongoDB, handles authentication (JWT), data storage, and WebSocket communication.
- Connection :
- HTTP requests (GET, POST, PUT) for regular communication.
- WebSockets for real-time updates on order status and delivery location.
Here’s how to write the entire backend for the client-side operations, server setup, authentication, real-time updates (availability, delivery status, etc.), and time left for food delivery using Node.js , Express , MongoDB , JWT authentication , and WebSockets .
-------------------------------------------------------------------------------------------------------------------------------
BACKEND SETUP
1. Server Setup :
You'll use Node.js and Express to set up the server, which handles routing, API calls, and WebSocket connections. The database will be MongoDB , and AWS EC2 will serve as the hosting platform.
Server Setup (Node.js and Express) :
```javascript
const express = require('express');
const mongoose = require('mongoose');
const bodyParser = require('body-parser');
const cors = require('cors');
const http = require('http');
const socketIo = require('socket.io');
const jwt = require('jsonwebtoken');
const app = express();
const server = http.createServer(app);
const io = socketIo(server);
// Middleware
app.use(bodyParser.json());
app.use(cors());
// MongoDB connection
mongoose.connect('mongodb://your-mongodb-uri', { useNewUrlParser: true, useUnifiedTopology: true })
.then(() => console.log('MongoDB connected'))
.catch(err => console.log(err));
// Secret Key for JWT
const JWT_SECRET = 'your_jwt_secret_key';
// Start Server
server.listen(3000, () => {
console.log('Server running on port 3000');
});
```
2. Client-Side Backend (API Routes)
User Model (MongoDB Schema) :
```javascript
const mongoose = require('mongoose');
const UserSchema = new mongoose.Schema({
email: String,
password: String,
role: { type: String, enum: ['client', 'restaurant', 'deliveryPartner'], default: 'client' },
});
module.exports = mongoose.model('User', UserSchema);
```
Authentication (JWT) :
- Signup, login, and token generation for the client. The token will be sent with each request to authorize actions like placing an order, etc.
```javascript
const bcrypt = require('bcryptjs');
const User = require('./models/User');
// Signup Route
app.post('/signup', async (req, res) => {
const { email, password } = req.body;
const hashedPassword = await bcrypt.hash(password, 10);
const user = new User({ email, password: hashedPassword });
await user.save();
res.json({ message: 'User registered' });
});
// Login Route
app.post('/login', async (req, res) => {
const { email, password } = req.body;
const user = await User.findOne({ email });
if (user && (await bcrypt.compare(password, user.password))) {
const token = jwt.sign({ userId: user._id, role: user.role }, JWT_SECRET);
res.json({ token });
} else {
res.status(401).json({ message: 'Invalid credentials' });
}
});
// Middleware to verify JWT
const verifyToken = (req, res, next) => {
const token = req.headers['authorization'];
if (!token) return res.status(403).send('Token is required');
try {
const decoded = jwt.verify(token.split(' ')[1], JWT_SECRET);
req.user = decoded;
next();
} catch (err) {
res.status(401).send('Invalid token');
}
};
```
Order Model (MongoDB Schema) :
```javascript
const OrderSchema = new mongoose.Schema({
userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
items: [{ name: String, quantity: Number }],
status: { type: String, enum: ['Placed', 'Preparing', 'Out for delivery', 'Delivered'], default: 'Placed' },
deliveryPartnerId: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: false },
deliveryLocation: String,
estimatedDeliveryTime: Date, // For time left calculations
});
module.exports = mongoose.model('Order', OrderSchema);
```
Placing an Order (Client-Side API) :
- The user sends a POST request to place an order, and the backend stores it in the database.
```javascript
const Order = require('./models/Order');
// Place Order
app.post('/order', verifyToken, async (req, res) => {
const { items, deliveryLocation } = req.body;
// Create new order
const order = new Order({
userId: req.user.userId,
items,
deliveryLocation,
estimatedDeliveryTime: calculateDeliveryTime(), // Assuming this function returns a future date
});
await order.save();
res.json({ message: 'Order placed', orderId: order._id });
});
// Function to calculate estimated delivery time (could be based on delivery partner location, etc.)
const calculateDeliveryTime = () => {
const currentTime = new Date();
const estimatedTime = new Date(currentTime.setMinutes(currentTime.getMinutes() + 45)); // Example: 45 mins delivery time
return estimatedTime;
};
```
3. Backend for Real-Time Updates (WebSockets)
WebSocket Setup for Real-Time Updates :
You'll use Socket.io to manage the WebSocket connection for real-time updates. This will notify the client about the status of their order and the delivery partner’s availability.
```javascript
io.on('connection', (socket) => {
console.log('New client connected');
// Listen for real-time delivery status updates from delivery partner
socket.on('updateDeliveryLocation', (data) => {
io.emit('deliveryLocation', data); // Emit delivery location update to clients
});
// Listen for order status changes from restaurant/admin side
socket.on('orderStatusUpdate', (data) => {
io.emit('orderStatus', data); // Emit order status update to client
});
socket.on('disconnect', () => {
console.log('Client disconnected');
});
});
```
4. Backend for Delivery Status :
- The delivery partner updates their location via WebSockets, and the backend emits the new location to the client.
- The restaurant/admin panel can update the order status (like "Out for delivery"), which will also be sent to the client in real time.
Updating Order Status (Restaurant/Admin) :
```javascript
// Update Order Status
app.put('/order/:orderId/status', verifyToken, async (req, res) => {
const { status } = req.body;
const { orderId } = req.params;
// Update order status
const order = await Order.findById(orderId);
order.status = status;
await order.save();
// Emit real-time update to the client
io.emit('orderStatus', { orderId, status });
res.json({ message: 'Order status updated' });
});
```
Delivery Partner Location Updates :
- The delivery partner app sends updates on location via WebSockets. These updates are emitted to the client for real-time tracking on the map.
```javascript
// Delivery partner sends location update
socket.on('locationUpdate', (data) => {
// Emit delivery location to client
io.emit('deliveryLocation', {
orderId: data.orderId,
location: data.location,
timeLeft: calculateTimeLeft(data.orderId),
});
});
// Function to calculate time left for delivery
const calculateTimeLeft = (orderId) => {
const order = Order.findById(orderId);
const currentTime = new Date();
const timeDifference = order.estimatedDeliveryTime - currentTime;
const minutesLeft = Math.floor(timeDifference / 60000); // Convert milliseconds to minutes
return minutesLeft;
};
```
5. Real-Time Updates for Delivery Time and Map :
- The client receives updates on the delivery partner’s location and the remaining time to delivery in real time via the WebSocket connection.
Emitting Time Left to Deliver :
Whenever the delivery partner updates their location, the time left for delivery is calculated based on the `estimatedDeliveryTime` in the order.
```javascript
// Frontend client receives location and time updates
socket.on('deliveryLocation', (data) => {
console.log(`New location: ${data.location}, Time left: ${data.timeLeft} minutes`);
// Update map and show time left in the UI
});
```
Complete Flow of Food Delivery Process :
1. User Places an Order :
- The client sends a request to the `/order` endpoint, and the order is stored in MongoDB with a status of "Placed".
2. Real-Time Updates :
- The delivery partner’s location and availability are sent to the client in real time via WebSockets.
- The restaurant can update the order status (e.g., "Preparing", "Out for delivery") via the admin panel, and these updates are pushed to the client in real time.
3. Map and Time Calculation :
- The delivery partner sends location updates, and the backend calculates the remaining time based on the current time and the `estimatedDeliveryTime` stored in the order.
- The client receives these updates and displays the delivery partner’s location on a map along with the estimated time remaining for delivery.
4. Delivery Completion :
- Once the order is marked as "Delivered", the process is complete, and the client is notified.
---
Key Tools Used :
Node.js & Express : Server setup and handling API requests.
MongoDB : Database for storing user and order details.
JWT : For secure authentication of users and API routes.
Varnish optimizes content delivery through caching.
Nginx acts as a reverse proxy for load balancing and serving static files.
Kafka handles real-time streaming, while WebSockets enable dynamic, live interactions.
With this stack, your web app will benefit from improved performance, scalability, and real-time capabilities, creating a smooth user experience.
OVERALL LAYOUT SETTING UP
Creating a robust and efficient web application involves integrating multiple technologies and tools for both the frontend and backend, each contributing to a smoother development experience, improved performance, and a more dynamic user experience. Here's how you can craft an awesome working environment for web app development with React Native , HTML , CSS , JavaScript , alongside a Node.js backend , JWT authentication , Varnish caching, Nginx , Kafka , and WebSockets for real-time communication.
1. Frontend with React Native, HTML, CSS, and JavaScript
- React Native : For mobile applications, React Native enables developers to build cross-platform apps using a single codebase. It provides access to native features while maintaining a high-performance, responsive user experience.
- HTML & CSS : These are foundational for building responsive web interfaces. Using modern CSS frameworks (like Tailwind or Bootstrap) can help streamline styling, making the UI more consistent and easier to manage.
- JavaScript : JavaScript is the heart of the frontend logic. Utilizing ES6+ features such as `async/await`, arrow functions, and modules can enhance code readability and performance.
Best Practices:
- Use React hooks like `useEffect`, `useState` to manage state efficiently in functional components.
- Integrate Styled Components for a cleaner CSS experience, especially in React Native.
- Implement React Navigation for smooth routing in mobile applications.
2. Backend with Node.js and JWT Authentication
- Node.js : Node.js, with its asynchronous and event-driven nature, is perfect for building scalable backend services. You can use Express.js as a framework to streamline the process of creating APIs.
- JWT (JSON Web Token) : JWT is ideal for handling user authentication in a stateless and secure manner. It allows secure API communication by generating tokens that are stored in local storage or cookies.
- During login, the server generates a JWT, which the client stores and includes in HTTP headers for authentication on subsequent requests.
- On the server, the token is verified on each request using middleware.
Code Snippet for JWT Middleware in Node.js:
```javascript
const jwt = require('jsonwebtoken');
const authenticateJWT = (req, res, next) => {
const token = req.header('Authorization');
if (!token) {
return res.status(403).send("Access denied.");
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (ex) {
res.status(400).send("Invalid token.");
}
};
```
3. Caching with Varnish for Faster Access
- Varnish : This is a reverse proxy and HTTP accelerator. It’s placed between the client and the backend server to cache frequently requested resources, significantly reducing load times and server strain. It’s ideal for caching static files and API responses, improving page load speeds.
- You can configure Varnish to cache certain HTTP requests and responses temporarily. For example, all GET requests for static files (images, CSS) or specific API responses can be cached for quicker subsequent access.
Varnish Configuration Example:
```bash
sub vcl_recv {
if (req.method == "GET") {
return (hash);
}
}
sub vcl_backend_response {
if (beresp.status == 200) {
set beresp.ttl = 24h; Cache for 24 hours
}
}
```
4. Nginx as the Backend Server
- Nginx : Acting as a reverse proxy, Nginx handles incoming client requests and forwards them to the Node.js server. It can also handle static file serving, SSL termination, and load balancing between multiple backend instances.
- Nginx works alongside Varnish, improving the performance and scalability of your web application.
Example Nginx Configuration:
```nginx
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /static/ {
root /var/www/html;
expires 30d; Cache static files for 30 days
}
}
```
5. Kafka for Real-Time Streaming and Messaging
- Apache Kafka : Kafka is a distributed streaming platform used for building real-time data pipelines and streaming applications. You can integrate Kafka to manage real-time streams, such as user activities, logs, or analytics data.
- Kafka topics are key components, representing streams of records. You can produce and consume messages from topics asynchronously, making Kafka suitable for use cases like event-driven systems, activity tracking, and real-time data feeds.
Example Use Case:
- In a chat application, Kafka can be used to stream messages. Each chat room could be a Kafka topic, and users publish messages to the topic (room), which is consumed by others in real-time.
6. WebSockets for Real-Time Bidirectional Communication
- WebSockets : For real-time, two-way communication between the server and clients, WebSockets are highly effective. Unlike HTTP, WebSockets maintain an open connection, enabling the server to push updates to the client without the need for the client to request new data.
- This is useful in scenarios like real-time chats, live notifications, and online games.
Using WebSockets in Node.js:
```javascript
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', ws => {
console.log('New client connected');
ws.on('message', message => {
console.log(`Received: ${message}`);
ws.send('Hello from server!');
});
ws.on('close', () => {
console.log('Client disconnected');
});
});
```
- WebSockets can also integrate with Kafka by sending messages between different Kafka topics in real-time, providing a seamless bidirectional flow of information.
7. Putting it All Together
- By using React Native on the frontend, you can build performant mobile apps.
- Node.js powers the backend with scalable, efficient services.
- JWT tokens ensure secure, stateless authentication.
- Varnish optimizes content delivery through caching.
- Nginx acts as a reverse proxy for load balancing and serving static files.
- Kafka handles real-time streaming, while WebSockets enable dynamic, live interactions.
With this stack, your web app will benefit from improved performance, scalability , and real-time capabilities , creating a smooth user experience.