In the fast-paced world of beauty and personal care, convenience and efficiency are key to meeting customer expectations and ensuring business success. Beauty salon booking apps have emerged as a transformative solution, offering a seamless experience for customers to book services and for salon owners to manage their businesses. These apps are designed with distinct user panels, each equipped with tailored features to enhance their specific experiences, thus creating a cohesive and comprehensive system that caters to the needs of all stakeholders involved.
This article explores the modular breakdown of a beauty salon booking app’s backend, focusing on the functionalities and features required to build a robust and user-friendly application.
Key Features for Different Panels
Customer Panel:
- User Registration: Streamlined and easy sign-up processes.
- Selecting Services: Enables users to choose preferred services and salons.
- Profiles: Allows users to view profiles of beauticians or salon professionals.
- Bookings: Facilitates the request and booking of appointments.
- Payment: Incorporates secure payment methods for seamless transactions.
- Reviews: Permits users to rate salons and experts based on service quality.
- Subscriptions: Offers subscription models for certain apps.
- Chat: Enables quick communication within the app.
Owner Panel:
- Registration: Allows the registration of one or multiple salons.
- Digital Catalog: Showcases a variety of services offered.
- User Requests: Manages appointment requests efficiently.
- Professional Profiles: Facilitates the addition and management of salon experts’ profiles.
- Service History: Provides a comprehensive view of past services for strategic management.
- Photo Uploads: Employees can post photos for vehicle owners using Multer and Cloudinary.
Admin Panel:
- User/Stylist/Salon Management: Exercises control over all profiles and actions.
- Approval: Approves or disapproves registrations based on specified criteria.
- Payment Management: Manages transactions and user payments securely.
- Monetization: Offers options for generating revenue through the app, such as ads, subscriptions, and promotions.
Additional Modules:
- Notification System: Manages booking notifications and SMS reminders.
- Billing System: Integrates Stripe for handling payments and billing.
- Revenue Model: Implements various monetization strategies.
- Analytics and Reporting: Provides insights and reports for business decisions.
- Testing and Deployment: Ensures the app is thoroughly tested and properly deployed.
Revenue Model of Salon Apps
The revenue generation for salon apps involves employing various strategies, with the primary objective being business expansion. Here are the ways a salon app can generate revenue:
- Prominent Listings: Enhancing visibility is crucial for salons. By offering special visibility options to salons and partners, the app can generate income.
- Commission Fees: Charging fees to beauty experts and salon owners for each transaction facilitated through the app.
- Subscription Packages: Users can purchase monthly packages for salon services, earning them reward points.
- Advertisements: Introducing ads within the app can be a source of revenue, with salon professionals or owners paying for these promotional opportunities.
- Discounted Subscriptions: Charging fees for subscriptions that provide discounted salon services.
Modular Breakdown for Development
To build a comprehensive and scalable beauty salon booking app, we will break down the development process into the following ten modules:
- User Authentication and Authorization Module
- Customer Panel Module
- Owner Panel Module
- Admin Panel Module
- Notification System Module
- Billing System Module
- Photo Upload Module
- Revenue Model Implementation Module
- Testing and Deployment Module
- Analytics and Reporting Module
Each module will be developed incrementally, ensuring a clear separation of concerns and efficient management of the development process. In the following sections, we will delve deeper into each module, outlining the key features and implementation strategies.
Module 1: User registration and authentication
Let’s start by setting up the first module: User Authentication and Authorization using MySQL. We will use Sequelize as the ORM (Object-Relational Mapping) tool to interact with MySQL.
Step-by-Step Guide to Build with MySQL
Step 1: Initialize the Node.js Application
Create a new directory for your project and navigate into it:
mkdir beauty-salon-app
cd beauty-salon-app
Initialize a new Node.js project:
npm init -y
Install the necessary dependencies:
npm install express dotenv mysql2 sequelize bcrypt jsonwebtoken body-parser helmet express-rate-limit winston swagger-ui-express swagger-jsdoc multer multer-storage-cloudinary twilio stripe redis
1. express
Description: Express is a minimal and flexible Node.js web application framework that provides a robust set of features to develop web and mobile applications.
Use:
- Setting up a web server and defining routes.
- Handling HTTP requests and responses.
2. dotenv
Description: Dotenv is a zero-dependency module that loads environment variables from a .env
file into process.env
.
Use:
- Managing configuration through environment variables.
- Keeping sensitive data like API keys out of your codebase.
3. mysql2
Description: Mysql2 is a fast and performant MySQL client for Node.js, supporting prepared statements, async/await, and promises.
Use:
- Connecting to and querying a MySQL database.
4. sequelize
Description: Sequelize is a promise-based Node.js ORM (Object-Relational Mapping) library for PostgreSQL, MySQL, MariaDB, SQLite, and MSSQL.
Use:
- Defining models that map to database tables.
- Performing CRUD operations without writing raw SQL.
5. bcrypt
Description: Bcrypt is a library to help you hash passwords.
Use:
- Securely hashing and storing passwords.
- Comparing hashed passwords for authentication.
6. jsonwebtoken
Description: Jsonwebtoken is a library to generate and verify JSON Web Tokens (JWT).
Use:
- Implementing authentication and authorization.
- Securing API endpoints.
7. body-parser
Description: Body-parser is a middleware to parse incoming request bodies in a middleware before your handlers, available under the req.body
property.
Use:
- Parsing JSON and URL-encoded data sent in HTTP requests.
8. helmet
Description: Helmet helps you secure your Express apps by setting various HTTP headers.
Use:
- Adding security headers to protect against common web vulnerabilities.
9. express-rate-limit
Description: Express-rate-limit is a middleware to limit repeated requests to public APIs and/or endpoints.
Use:
- Protecting your app from brute-force attacks and DoS (Denial of Service) attacks.
10. winston
Description: Winston is a versatile logging library with support for multiple transports (e.g., console, files).
Use:
- Logging application activity and errors.
- Creating logs for debugging and monitoring.
11. swagger-ui-express
Description: Swagger-ui-express is a package that allows you to serve auto-generated Swagger API documentation from your Express server.
Use:
- Providing interactive API documentation for developers.
12. swagger-jsdoc
Description: Swagger-jsdoc is a library that parses JSDoc comments to generate OpenAPI (Swagger) documentation.
Use:
- Automatically generating Swagger documentation from your code comments.
13. multer
Description: Multer is a Node.js middleware for handling multipart/form-data
, primarily used for uploading files.
Use:
- Handling file uploads in your application.
14. multer-storage-cloudinary
Description: Multer-storage-cloudinary is a storage engine for Multer to upload files directly to Cloudinary.
Use:
- Storing uploaded files in Cloudinary, a cloud-based media management service.
15. twilio
Description: Twilio is a library that allows you to send SMS, make voice calls, and use other Twilio services.
Use:
- Sending SMS notifications and alerts.
16. stripe
Description: Stripe is a library that provides a powerful API for processing payments.
Use:
- Handling online payments and billing in your application.
17. redis
Description: Redis is a fast, in-memory data structure store, used as a database, cache, and message broker.
Use:
- Caching frequently accessed data to improve performance.
- Managing session data and other transient information.
Step 2: Project Structure
Let’s define the project structure to keep everything organized:
beauty-salon-app/
│
├── .env
├── package.json
├── server.js
├── config/
│ └── db.js
│ └── logger.js
│ └── redis.js
│ └── swagger.js
├── controllers/
│ └── auth.js
├── middleware/
│ └── auth.js
├── models/
│ └── index.js
│ └── user.js
├── routes/
│ └── auth.js
└── utils/
└── payment.js
└── sms.js
└── cloudinary.js
Step 3: Environment Variables
Create a file named .env
in the root directory to store environment variables.
File: .env
PORT=3000
DB_HOST=localhost
DB_USER=root
DB_PASS=your_mysql_password
DB_NAME=beauty_salon_app
JWT_SECRET=your_jwt_secret_key
- PORT: The port number where the server will run.
- DB_HOST, DB_USER, DB_PASS, DB_NAME: MySQL database connection details.
- JWT_SECRET: A secret key used to sign JWT tokens for security.
Step 4: Configure Sequelize and Database Connection
Create a file to configure Sequelize and connect to MySQL.
File: config/db.js
const { Sequelize } = require('sequelize');
const dotenv = require('dotenv');
dotenv.config();
const sequelize = new Sequelize(process.env.DB_NAME, process.env.DB_USER, process.env.DB_PASS, {
host: process.env.DB_HOST,
dialect: 'mysql',
});
sequelize.authenticate()
.then(() => {
console.log('Connection to MySQL has been established successfully.');
})
.catch(err => {
console.error('Unable to connect to the database:', err);
});
module.exports = sequelize;
Step 5: Define the User Model
Create a file to define the User model using Sequelize.
File: models/user.js
const { Sequelize, DataTypes } = require('sequelize');
const sequelize = require('../config/db');
const User = sequelize.define('User', {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
username: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
},
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
},
password: {
type: DataTypes.STRING,
allowNull: false,
},
role: {
type: DataTypes.ENUM('Customer', 'Owner', 'Admin'),
allowNull: false,
},
}, {
timestamps: true,
});
module.exports = User;
Step 6: Initialize Sequelize Models
Create an index file to initialize all Sequelize models.
File: models/index.js
const sequelize = require('../config/db');
const User = require('./user');
const initModels = async () => {
await sequelize.sync({ force: true }); // For development purposes, use { force: true } to drop and recreate tables
console.log('All models were synchronized successfully.');
};
initModels();
module.exports = {
User,
};
Step 7: Implement the Authentication Controller
Create a file to handle the logic for user registration and login.
File: controllers/auth.js
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const { User } = require('../models');
// Controller for user registration
exports.register = async (req, res) => {
const { username, email, password, role } = req.body;
try {
const hashedPassword = await bcrypt.hash(password, 10);
const newUser = await User.create({
username,
email,
password: hashedPassword,
role,
});
res.status(201).json({ message: 'User registered successfully' });
} catch (error) {
res.status(400).json({ error: error.message });
}
};
// Controller for user login
exports.login = async (req, res) => {
const { email, password } = req.body;
try {
const user = await User.findOne({ where: { email } });
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch) {
return res.status(400).json({ error: 'Invalid credentials' });
}
const token = jwt.sign({ id: user.id, role: user.role }, process.env.JWT_SECRET, {
expiresIn: '1h',
});
res.json({ token });
} catch (error) {
res.status(500).json({ error: error.message });
}
};
Step 8: Create Middleware for Authentication
Create a file to define middleware functions for authentication and authorization.
File: middleware/auth.js
const jwt = require('jsonwebtoken');
const authMiddleware = (req, res, next) => {
const token = req.header('Authorization').replace('Bearer ', '');
if (!token) {
return res.status(401).json({ error: 'Access denied' });
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
res.status(400).json({ error: 'Invalid token' });
}
};
const roleMiddleware = (requiredRole) => (req, res, next) => {
if (req.user.role !== requiredRole) {
return res.status(403).json({ error: 'Access denied' });
}
next();
};
module.exports = { authMiddleware, roleMiddleware };
Step 9: Define Authentication Routes
Create a file to define the authentication routes.
File: routes/auth.js
const express = require('express');
const { register, login } = require('../controllers/auth');
const authRouter = express.Router();
authRouter.post('/register', register);
authRouter.post('/login', login);
module.exports = authRouter;
Step 10: Initialize the Express Application
Create the main server file to initialize the Express application and connect to MySQL.
File: server.js
const express = require('express');
const dotenv = require('dotenv');
const connectDB = require('./config/db');
const authRouter = require('./routes/auth');
const { initModels } = require('./models');
dotenv.config();
const app = express();
app.use(express.json());
app.use('/auth', authRouter);
const PORT = process.env.PORT || 3000;
app.listen(PORT, async () => {
console.log(`Server is running on port ${PORT}`);
await initModels();
});
connectDB();
By following this step-by-step guide, you have implemented the first module, User Authentication and Authorization, using MySQL and Sequelize in the beauty salon booking app. The steps include initializing the project, setting up the environment variables, configuring Sequelize, defining models, implementing controllers, creating middleware, and setting up routes. This forms the foundation for further modules.
Module 2: Customer Panel
The Customer Panel module provides functionalities for customers to interact with the beauty salon booking app. This includes service selection, profile management, booking management, payment processing, reviews, subscriptions, and chat functionality.
Step-by-Step Guide
Step 1: Define the Service and Booking Models
Create schemas for services and bookings.
File: models/service.js
const { Sequelize, DataTypes } = require('sequelize');
const sequelize = require('../config/db');
const User = require('./user');
const Service = sequelize.define('Service', {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
name: {
type: DataTypes.STRING,
allowNull: false,
},
description: {
type: DataTypes.STRING,
allowNull: false,
},
price: {
type: DataTypes.FLOAT,
allowNull: false,
},
duration: {
type: DataTypes.INTEGER, // Duration in minutes
allowNull: false,
},
createdBy: {
type: DataTypes.INTEGER,
references: {
model: User,
key: 'id',
},
},
}, {
timestamps: true,
});
module.exports = Service;
File: models/booking.js
const { Sequelize, DataTypes } = require('sequelize');
const sequelize = require('../config/db');
const User = require('./user');
const Service = require('./service');
const Booking = sequelize.define('Booking', {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
serviceId: {
type: DataTypes.INTEGER,
references: {
model: Service,
key: 'id',
},
},
customerId: {
type: DataTypes.INTEGER,
references: {
model: User,
key: 'id',
},
},
bookingDate: {
type: DataTypes.DATE,
allowNull: false,
},
status: {
type: DataTypes.ENUM('Pending', 'Confirmed', 'Cancelled'),
defaultValue: 'Pending',
},
}, {
timestamps: true,
});
module.exports = Booking;
Step 2: Initialize Sequelize Models
Update the index file to initialize all Sequelize models.
File: models/index.js
const sequelize = require('../config/db');
const User = require('./user');
const Service = require('./service');
const Booking = require('./booking');
const initModels = async () => {
await sequelize.sync({ force: true }); // For development purposes, use { force: true } to drop and recreate tables
console.log('All models were synchronized successfully.');
};
initModels();
module.exports = {
User,
Service,
Booking,
};
Step 3: Implement the Customer Controller
Create a file to handle the logic for customer-related operations.
File: controllers/customer.js
const { Service, Booking } = require('../models');
// Controller to get all services
exports.getAllServices = async (req, res) => {
try {
const services = await Service.findAll();
res.json(services);
} catch (error) {
res.status(500).json({ error: error.message });
}
};
// Controller to book a service
exports.bookService = async (req, res) => {
const { serviceId, bookingDate } = req.body;
try {
const booking = await Booking.create({
serviceId,
customerId: req.user.id,
bookingDate,
});
res.status(201).json({ message: 'Service booked successfully', booking });
} catch (error) {
res.status(500).json({ error: error.message });
}
};
// Controller to get customer bookings
exports.getCustomerBookings = async (req, res) => {
try {
const bookings = await Booking.findAll({ where: { customerId: req.user.id }, include: [Service] });
res.json(bookings);
} catch (error) {
res.status(500).json({ error: error.message });
}
};
Step 4: Create Middleware for Authentication
Ensure the existing authentication middleware is applied to customer routes.
File: middleware/auth.js
const jwt = require('jsonwebtoken');
const authMiddleware = (req, res, next) => {
const token = req.header('Authorization').replace('Bearer ', '');
if (!token) {
return res.status(401).json({ error: 'Access denied' });
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
res.status(400).json({ error: 'Invalid token' });
}
};
const roleMiddleware = (requiredRole) => (req, res, next) => {
if (req.user.role !== requiredRole) {
return res.status(403).json({ error: 'Access denied' });
}
next();
};
module.exports = { authMiddleware, roleMiddleware };
Step 5: Define Customer Routes
Create a file to define the routes for customer-related operations.
File: routes/customer.js
const express = require('express');
const { getAllServices, bookService, getCustomerBookings } = require('../controllers/customer');
const { authMiddleware, roleMiddleware } = require('../middleware/auth');
const customerRouter = express.Router();
// Route to get all services
customerRouter.get('/services', authMiddleware, roleMiddleware('Customer'), getAllServices);
// Route to book a service
customerRouter.post('/book', authMiddleware, roleMiddleware('Customer'), bookService);
// Route to get customer bookings
customerRouter.get('/bookings', authMiddleware, roleMiddleware('Customer'), getCustomerBookings);
module.exports = customerRouter;
- customerRouter.get(‘/services’): Defines the route to get all services.
- customerRouter.post(‘/book’): Defines the route to book a service.
- customerRouter.get(‘/bookings’): Defines the route to get customer bookings.
- authMiddleware, roleMiddleware(‘Customer’): Ensures the routes are accessible only to authenticated customers.
Step 6: Integrate Customer Routes in the Main Server File
Update the server.js
file to include the customer routes.
File: server.js
const express = require('express');
const dotenv = require('dotenv');
const connectDB = require('./config/db');
const authRouter = require('./routes/auth');
const customerRouter = require('./routes/customer');
const { initModels } = require('./models');
dotenv.config();
const app = express();
app.use(express.json());
app.use('/auth', authRouter);
app.use('/customer', customerRouter);
const PORT = process.env.PORT || 3000;
app.listen(PORT, async () => {
console.log(`Server is running on port ${PORT}`);
await initModels();
});
connectDB();
- app.use(‘/customer’, customerRouter): Mounts the customer routes at
/customer
.
Step 7: Implement Payment Processing Utility
Create a utility file to handle payment processing using Stripe.
File: utils/payment.js
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
exports.createPaymentIntent = async (amount) => {
try {
const paymentIntent = await stripe.paymentIntents.create({
amount,
currency: 'usd',
});
return paymentIntent;
} catch (error) {
throw new Error(error.message);
}
};
- createPaymentIntent: Creates a payment intent using Stripe.
- amount: The amount to be charged.
By following this detailed structure and code explanation, you can implement a comprehensive Customer Panel module for the beauty salon booking app using MySQL and Sequelize. This module provides functionalities for service selection, booking management, and payment processing, ensuring a seamless experience for customers. The step-by-step explanations help beginners understand each part of the code, its purpose, and how it fits into the larger application.
Module 3: Owner Panel
The Owner Panel module provides functionalities for salon owners to manage their salons, services, and appointments. This includes salon registration, service catalog management, appointment management, professional profile management, and service history tracking.
Step-by-Step Guide
Step 1: Define the Salon Model
Create a schema for salons.
File: models/salon.js
const { Sequelize, DataTypes } = require('sequelize');
const sequelize = require('../config/db');
const User = require('./user');
const Salon = sequelize.define('Salon', {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
name: {
type: DataTypes.STRING,
allowNull: false,
},
address: {
type: DataTypes.STRING,
allowNull: false,
},
phone: {
type: DataTypes.STRING,
allowNull: false,
},
ownerId: {
type: DataTypes.INTEGER,
references: {
model: User,
key: 'id',
},
},
}, {
timestamps: true,
});
module.exports = Salon;
Explanation:
- Salon Schema: Defines the structure of the salon table.
- id: Primary key, auto-incrementing integer.
- name, address, phone: Required fields for the salon’s details.
- ownerId: Foreign key referencing the User table to link the salon to its owner.
- timestamps: Automatically add
createdAt
andupdatedAt
fields.
Step 2: Define the Association between Models
Update the index file to define associations between models.
File: models/index.js
const sequelize = require('../config/db');
const User = require('./user');
const Service = require('./service');
const Booking = require('./booking');
const Salon = require('./salon');
User.hasMany(Salon, { foreignKey: 'ownerId' });
Salon.belongsTo(User, { foreignKey: 'ownerId' });
Salon.hasMany(Service, { foreignKey: 'salonId' });
Service.belongsTo(Salon, { foreignKey: 'salonId' });
Service.hasMany(Booking, { foreignKey: 'serviceId' });
Booking.belongsTo(Service, { foreignKey: 'serviceId' });
User.hasMany(Booking, { foreignKey: 'customerId' });
Booking.belongsTo(User, { foreignKey: 'customerId' });
const initModels = async () => {
await sequelize.sync({ force: true }); // For development purposes, use { force: true } to drop and recreate tables
console.log('All models were synchronized successfully.');
};
initModels();
module.exports = {
User,
Service,
Booking,
Salon,
};
Explanation:
- Associations:
- User and Salon: One-to-many relationship where a user (owner) can have multiple salons.
- Salon and Service: One-to-many relationship where a salon can offer multiple services.
- Service and Booking: One-to-many relationship where a service can have multiple bookings.
- User and Booking: One-to-many relationship where a user (customer) can have multiple bookings.
Step 3: Implement the Owner Controller
Create a file to handle the logic for owner-related operations.
File: controllers/owner.js
const { Salon, Service, Booking } = require('../models');
// Controller to register a new salon
exports.registerSalon = async (req, res) => {
const { name, address, phone } = req.body;
try {
const newSalon = await Salon.create({
name,
address,
phone,
ownerId: req.user.id,
});
res.status(201).json({ message: 'Salon registered successfully', newSalon });
} catch (error) {
res.status(500).json({ error: error.message });
}
};
// Controller to add a new service to a salon
exports.addService = async (req, res) => {
const { name, description, price, duration, salonId } = req.body;
try {
const newService = await Service.create({
name,
description,
price,
duration,
salonId,
});
res.status(201).json({ message: 'Service added successfully', newService });
} catch (error) {
res.status(500).json({ error: error.message });
}
};
// Controller to get all services of a salon
exports.getSalonServices = async (req, res) => {
const { salonId } = req.params;
try {
const services = await Service.findAll({ where: { salonId } });
res.json(services);
} catch (error) {
res.status(500).json({ error: error.message });
}
};
// Controller to manage appointments
exports.getAppointments = async (req, res) => {
try {
const appointments = await Booking.findAll({ where: { salonId: req.params.salonId }, include: [Service, { model: User, as: 'customer' }] });
res.json(appointments);
} catch (error) {
res.status(500).json({ error: error.message });
}
};
// Controller to get service history
exports.getServiceHistory = async (req, res) => {
try {
const history = await Booking.findAll({ where: { salonId: req.params.salonId }, include: [Service, { model: User, as: 'customer' }] });
res.json(history);
} catch (error) {
res.status(500).json({ error: error.message });
}
};
Explanation:
- registerSalon: Registers a new salon for the authenticated owner.
- req.body: Contains the salon details (name, address, phone).
- req.user.id: The ID of the authenticated owner.
- addService: Adds a new service to a specific salon.
- req.body: Contains the service details (name, description, price, duration, salonId).
- getSalonServices: Retrieves all services offered by a specific salon.
- req.params.salonId: The ID of the salon.
- getAppointments: Retrieves all appointments for services provided by the authenticated owner’s salon.
- req.params.salonId: The ID of the salon.
- getServiceHistory: Retrieves the service history for the authenticated owner’s salon.
- req.params.salonId: The ID of the salon.
Step 4: Create Middleware for Authentication
Ensure the existing authentication middleware is applied to owner routes.
File: middleware/auth.js
const jwt = require('jsonwebtoken');
const authMiddleware = (req, res, next) => {
const token = req.header('Authorization').replace('Bearer ', '');
if (!token) {
return res.status(401).json({ error: 'Access denied' });
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
res.status(400).json({ error: 'Invalid token' });
}
};
const roleMiddleware = (requiredRole) => (req, res, next) => {
if (req.user.role !== requiredRole) {
return res.status(403).json({ error: 'Access denied' });
}
next();
};
module.exports = { authMiddleware, roleMiddleware };
Explanation:
- authMiddleware: Verifies the JWT token and attaches the decoded user information to the request object.
- req.header(‘Authorization’): Retrieves the token from the Authorization header.
- jwt.verify(): Verifies the token using the secret key.
- roleMiddleware: Checks the user’s role against the required role for the route.
- requiredRole: The role required to access the route.
Step 5: Define Owner Routes
Create a file to define the routes for owner-related operations.
File: routes/owner.js
const express = require('express');
const { registerSalon, addService, getSalonServices, getAppointments, getServiceHistory } = require('../controllers/owner');
const { authMiddleware, roleMiddleware } = require('../middleware/auth');
const ownerRouter = express.Router();
// Route to register a new salon
ownerRouter.post('/register-salon', authMiddleware, roleMiddleware('Owner'), registerSalon);
// Route to add a new service
ownerRouter.post('/add-service', authMiddleware, roleMiddleware('Owner'), addService);
// Route to get all services of a salon
ownerRouter.get('/salon-services/:salonId', authMiddleware, roleMiddleware('Owner'), getSalonServices);
// Route to manage appointments
ownerRouter.get('/appointments/:salonId', authMiddleware, roleMiddleware('Owner'), getAppointments);
// Route to get service history
ownerRouter.get('/service-history/:salonId', authMiddleware, roleMiddleware('Owner'), getServiceHistory);
module.exports = ownerRouter;
Explanation:
- ownerRouter.post(‘/register-salon’): Defines the route to register a new salon.
- ownerRouter.post(‘/add-service’): Defines the route to add a new service to a salon.
- ownerRouter.get(‘/salon-services/
- ownerRouter.get(‘/appointments/
- ownerRouter.get(‘/service-history/
- authMiddleware, roleMiddleware(‘Owner’): Ensures the routes are accessible only to authenticated salon owners.
Step 6: Integrate Owner Routes in the Main Server
Update the server.js
file to include the owner routes.
File: server.js
const express = require('express');
const dotenv = require('dotenv');
const connectDB = require('./config/db');
const authRouter = require('./routes/auth');
const customerRouter = require('./routes/customer');
const ownerRouter = require('./routes/owner');
const { initModels } = require('./models');
dotenv.config();
const app = express();
app.use(express.json());
app.use('/auth', authRouter);
app.use('/customer', customerRouter);
app.use('/owner', ownerRouter);
const PORT = process.env.PORT || 3000;
app.listen(PORT, async () => {
console.log(`Server is running on port ${PORT}`);
await initModels();
});
connectDB();
Explanation:
- app.use(‘/owner’, ownerRouter): Mounts the owner routes at
/owner
.
By following this detailed structure and code explanation, you can implement a comprehensive Owner Panel module for the beauty salon booking app using MySQL and Sequelize. This module provides functionalities for salon registration, service catalog management, appointment management, and service history tracking, ensuring a seamless experience for salon owners. The step-by-step explanations help beginners understand each part of the code, its purpose, and how it fits into the larger application.
Module 4: Admin Panel
The Admin Panel module provides functionalities for administrators to manage users, approve or disapprove registrations, manage payments, and oversee the monetization strategies of the beauty salon booking app.
Step-by-Step Guide
Step 1: Define the Admin-Specific Models
Since the admin module deals primarily with managing existing models, we will use the existing models (User, Salon, Service, Booking) and add admin-specific functionalities.
Step 2: Implement the Admin Controller
Create a file to handle the logic for admin-related operations.
File: controllers/admin.js
const { User, Salon, Booking, Revenue } = require('../models');
// Controller to get all users
exports.getAllUsers = async (req, res) => {
try {
const users = await User.findAll();
res.json(users);
} catch (error) {
res.status(500).json({ error: error.message });
}
};
// Controller to get all salons
exports.getAllSalons = async (req, res) => {
try {
const salons = await Salon.findAll({ include: [User] });
res.json(salons);
} catch (error) {
res.status(500).json({ error: error.message });
}
};
// Controller to approve or disapprove a salon
exports.approveSalon = async (req, res) => {
const { salonId, approved } = req.body;
try {
const salon = await Salon.findByPk(salonId);
if (!salon) {
return res.status(404).json({ error: 'Salon not found' });
}
salon.approved = approved;
await salon.save();
res.json({ message: `Salon ${approved ? 'approved' : 'disapproved'} successfully`, salon });
} catch (error) {
res.status(500).json({ error: error.message });
}
};
// Controller to get all bookings
exports.getAllBookings = async (req, res) => {
try {
const bookings = await Booking.findAll({ include: [Service, { model: User, as: 'customer' }] });
res.json(bookings);
} catch (error) {
res.status(500).json({ error: error.message });
}
};
// Controller to manage payments
exports.managePayments = async (req, res) => {
// Example implementation for managing payments
try {
// Implement payment management logic here
res.json({ message: 'Payment management feature to be implemented' });
} catch (error) {
res.status(500).json({ error: error.message });
}
};
Explanation:
- getAllUsers: Retrieves all users in the system.
- getAllSalons: Retrieves all salons in the system.
- approveSalon: Approves or disapproves a salon.
- req.body: Contains the approval data (salonId, approved).
- getAllBookings: Retrieves all bookings in the system.
- managePayments: Placeholder for managing payments.
Step 3: Create Middleware for Authentication
Ensure the existing authentication middleware is applied to admin routes.
File: middleware/auth.js
const jwt = require('jsonwebtoken');
const authMiddleware = (req, res, next) => {
const token = req.header('Authorization').replace('Bearer ', '');
if (!token) {
return res.status(401).json({ error: 'Access denied' });
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
res.status(400).json({ error: 'Invalid token' });
}
};
const roleMiddleware = (requiredRole) => (req, res, next) => {
if (req.user.role !== requiredRole) {
return res.status(403).json({ error: 'Access denied' });
}
next();
};
module.exports = { authMiddleware, roleMiddleware };
Explanation:
- authMiddleware: Verifies the JWT token and attaches the decoded user information to the request object.
- req.header(‘Authorization’): Retrieves the token from the Authorization header.
- jwt.verify(): Verifies the token using the secret key.
- roleMiddleware: Checks the user’s role against the required role for the route.
- requiredRole: The role required to access the route.
Step 4: Define Admin Routes
Create a file to define the routes for admin-related operations.
File: routes/admin.js
const express = require('express');
const { getAllUsers, getAllSalons, approveSalon, getAllBookings, managePayments } = require('../controllers/admin');
const { authMiddleware, roleMiddleware } = require('../middleware/auth');
const adminRouter = express.Router();
// Route to get all users
adminRouter.get('/users', authMiddleware, roleMiddleware('Admin'), getAllUsers);
// Route to get all salons
adminRouter.get('/salons', authMiddleware, roleMiddleware('Admin'), getAllSalons);
// Route to approve or disapprove a salon
adminRouter.post('/approve-salon', authMiddleware, roleMiddleware('Admin'), approveSalon);
// Route to get all bookings
adminRouter.get('/bookings', authMiddleware, roleMiddleware('Admin'), getAllBookings);
// Route to manage payments
adminRouter.post('/manage-payments', authMiddleware, roleMiddleware('Admin'), managePayments);
module.exports = adminRouter;
Explanation:
- adminRouter.get(‘/users’): Defines the route to get all users.
- adminRouter.get(‘/salons’): Defines the route to get all salons.
- adminRouter.post(‘/approve-salon’): Defines the route to approve or disapprove a salon.
- adminRouter.get(‘/bookings’): Defines the route to get all bookings.
- adminRouter.post(‘/manage-payments’): Defines the route to manage payments.
- authMiddleware, roleMiddleware(‘Admin’): Ensures the routes are accessible only to authenticated administrators.
Step 5: Integrate Admin Routes in the Main Server File
Update the server.js
file to include the admin routes.
File: server.js
const express = require('express');
const dotenv = require('dotenv');
const connectDB = require('./config/db');
const authRouter = require('./routes/auth');
const customerRouter = require('./routes/customer');
const ownerRouter = require('./routes/owner');
const adminRouter = require('./routes/admin');
const { initModels } = require('./models');
dotenv.config();
const app = express();
app.use(express.json());
app.use('/auth', authRouter);
app.use('/customer', customerRouter);
app.use('/owner', ownerRouter);
app.use('/admin', adminRouter);
const PORT = process.env.PORT || 3000;
app.listen(PORT, async () => {
console.log(`Server is running on port ${PORT}`);
await initModels();
});
connectDB();
Explanation:
- app.use(‘/admin’, adminRouter): Mounts the admin routes at
/admin
.
By following this detailed structure and code explanation, you can implement a comprehensive Admin Panel module for the beauty salon booking app using MySQL and Sequelize. This module provides functionalities for user management, salon approval, booking management, and payment oversight, ensuring administrators have full control over the system. The step-by-step explanations help beginners understand each part of the code, its purpose, and how it fits into the larger application.
Module 5: Notification System
The Notification System module provides functionalities to handle notifications for bookings and reminders. This includes sending notifications when bookings are accepted and sending SMS reminders one day before appointments.
Step-by-Step Guide
Step 1: Install Twilio
Twilio will be used for sending SMS notifications.
npm install twilio
Step 2: Configure Environment Variables
Add Twilio-related environment variables to the .env
file.
File: .env
TWILIO_ACCOUNT_SID=your_twilio_account_sid
TWILIO_AUTH_TOKEN=your_twilio_auth_token
TWILIO_PHONE_NUMBER=your_twilio_phone_number
Explanation:
- TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, TWILIO_PHONE_NUMBER: Credentials and phone number provided by Twilio for sending SMS.
Step 3: Implement the Notification Controller
Create a file to handle the logic for notification-related operations.
File: controllers/notification.js
const Booking = require('../models/booking');
const User = require('../models/user');
const { sendSMS } = require('../utils/sms');
// Controller to notify customer about booking confirmation
exports.notifyBookingConfirmation = async (req, res) => {
const { bookingId } = req.body;
try {
const booking = await Booking.findByPk(bookingId, { include: [User] });
if (!booking) {
return res.status(404).json({ error: 'Booking not found' });
}
// Send SMS notification
const message = `Dear ${booking.User.username}, your booking for ${booking.bookingDate} has been confirmed.`;
await sendSMS(booking.User.phone, message);
res.json({ message: 'Booking confirmation notification sent successfully' });
} catch (error) {
res.status(500).json({ error: error.message });
}
};
// Controller to send SMS reminders one day before the booking date
exports.sendReminder = async (req, res) => {
const { bookingId } = req.body;
try {
const booking = await Booking.findByPk(bookingId, { include: [User] });
if (!booking) {
return res.status(404).json({ error: 'Booking not found' });
}
const bookingDate = new Date(booking.bookingDate);
const reminderDate = new Date();
reminderDate.setDate(reminderDate.getDate() + 1);
if (bookingDate.toDateString() === reminderDate.toDateString()) {
const message = `Reminder: Dear ${booking.User.username}, you have a booking scheduled for tomorrow at ${bookingDate.toLocaleTimeString()}.`;
await sendSMS(booking.User.phone, message);
res.json({ message: 'Reminder sent successfully' });
} else {
res.status(400).json({ message: 'No bookings for tomorrow to send reminders' });
}
} catch (error) {
res.status(500).json({ error: error.message });
}
};
Explanation:
- notifyBookingConfirmation: Sends an SMS notification to the customer when their booking is confirmed.
- req.body: Contains the booking ID.
- sendReminder: Sends an SMS reminder to the customer one day before their booking.
- req.body: Contains the booking ID.
Step 4: Implement the SMS Utility
Create a utility file to handle SMS sending using Twilio.
File: utils/sms.js
const twilio = require('twilio');
const dotenv = require('dotenv');
dotenv.config();
const client = twilio(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN);
exports.sendSMS = async (to, body) => {
try {
const message = await client.messages.create({
body,
from: process.env.TWILIO_PHONE_NUMBER,
to,
});
return message;
} catch (error) {
throw new Error('Failed to send SMS');
}
};
Explanation:
- sendSMS: Sends an SMS using Twilio.
- to: The recipient’s phone number.
- body: The message to be sent.
Step 5: Define Notification Routes
Create a file to define the routes for notification-related operations.
File: routes/notification.js
const express = require('express');
const { notifyBookingConfirmation, sendReminder } = require('../controllers/notification');
const { authMiddleware, roleMiddleware } = require('../middleware/auth');
const notificationRouter = express.Router();
// Route to notify customer about booking confirmation
notificationRouter.post('/notify-booking', authMiddleware, roleMiddleware('Owner'), notifyBookingConfirmation);
// Route to send SMS reminders one day before the booking date
notificationRouter.post('/send-reminder', authMiddleware, roleMiddleware('Owner'), sendReminder);
module.exports = notificationRouter;
Explanation:
- notificationRouter.post(‘/notify-booking’): Defines the route to notify customers about booking confirmation.
- notificationRouter.post(‘/send-reminder’): Defines the route to send SMS reminders one day before the booking date.
- authMiddleware, roleMiddleware(‘Owner’): Ensures the routes are accessible only to authenticated salon owners.
Step 6: Integrate Notification Routes in the Main Server File
Update the server.js
file to include the notification routes.
File: server.js
const express = require('express');
const dotenv = require('dotenv');
const connectDB = require('./config/db');
const authRouter = require('./routes/auth');
const customerRouter = require('./routes/customer');
const ownerRouter = require('./routes/owner');
const adminRouter = require('./routes/admin');
const notificationRouter = require('./routes/notification');
const { initModels } = require('./models');
dotenv.config();
const app = express();
app.use(express.json());
app.use('/auth', authRouter);
app.use('/customer', customerRouter);
app.use('/owner', ownerRouter);
app.use('/admin', adminRouter);
app.use('/notification', notificationRouter);
const PORT = process.env.PORT || 3000;
app.listen(PORT, async () => {
console.log(`Server is running on port ${PORT}`);
await initModels();
});
connectDB();
Explanation:
- app.use(‘/notification’, notificationRouter): Mounts the notification routes at
/notification
.
By following this detailed structure and code explanation, you can implement a comprehensive Notification System module for the beauty salon booking app. This module provides functionalities to send booking confirmations and reminders via SMS, ensuring a seamless experience for customers. The step-by-step explanations help beginners understand each part of the code, its purpose, and how it fits into the larger application.
Module 6: Billing System
The Billing System module provides functionalities to handle billing and payments for the beauty salon booking app. This includes creating payment intents using Stripe and managing billing history.
Step-by-Step Guide
Step 1: Install Stripe
Stripe will be used for handling payments.
npm install stripe
Step 2: Configure Environment Variables
Add Stripe-related environment variables to the .env
file.
File: .env
STRIPE_SECRET_KEY=your_stripe_secret_key
Explanation:
- STRIPE_SECRET_KEY: The secret key provided by Stripe for API access.
Step 3: Define the Billing Model
Create a schema for billing records.
File: models/billing.js
const { Sequelize, DataTypes } = require('sequelize');
const sequelize = require('../config/db');
const User = require('./user');
const Billing = sequelize.define('Billing', {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
customerId: {
type: DataTypes.INTEGER,
references: {
model: User,
key: 'id',
},
},
amount: {
type: DataTypes.FLOAT,
allowNull: false,
},
paymentIntentId: {
type: DataTypes.STRING,
allowNull: false,
},
status: {
type: DataTypes.STRING,
allowNull: false,
},
createdAt: {
type: DataTypes.DATE,
defaultValue: DataTypes.NOW,
},
}, {
timestamps: false,
});
module.exports = Billing;
Explanation:
- billingSchema: Defines the structure of billing records.
- customerId: References the user who made the payment.
- amount: The amount paid.
- paymentIntentId: The ID of the Stripe payment intent.
- status: The status of the payment.
- createdAt: The date and time when the billing record was created.
Step 4: Initialize Sequelize Models
Update the index file to initialize all Sequelize models, including the new Billing model.
File: models/index.js
const sequelize = require('../config/db');
const User = require('./user');
const Service = require('./service');
const Booking = require('./booking');
const Salon = require('./salon');
const Billing = require('./billing');
User.hasMany(Salon, { foreignKey: 'ownerId' });
Salon.belongsTo(User, { foreignKey: 'ownerId' });
Salon.hasMany(Service, { foreignKey: 'salonId' });
Service.belongsTo(Salon, { foreignKey: 'salonId' });
Service.hasMany(Booking, { foreignKey: 'serviceId' });
Booking.belongsTo(Service, { foreignKey: 'serviceId' });
User.hasMany(Booking, { foreignKey: 'customerId' });
Booking.belongsTo(User, { foreignKey: 'customerId' });
User.hasMany(Billing, { foreignKey: 'customerId' });
Billing.belongsTo(User, { foreignKey: 'customerId' });
const initModels = async () => {
await sequelize.sync({ force: true }); // For development purposes, use { force: true } to drop and recreate tables
console.log('All models were synchronized successfully.');
};
initModels();
module.exports = {
User,
Service,
Booking,
Salon,
Billing,
};
Explanation:
- Associations:
- User and Billing: One-to-many relationship where a user (customer) can have multiple billing records.
Step 5: Implement the Billing Controller
Create a file to handle the logic for billing-related operations.
File: controllers/billing.js
const { Billing } = require('../models');
const { createPaymentIntent } = require('../utils/payment');
// Controller to create a payment intent
exports.createPayment = async (req, res) => {
const { amount } = req.body;
try {
const paymentIntent = await createPaymentIntent(amount);
res.status(201).json({ clientSecret: paymentIntent.client_secret });
} catch (error) {
res.status(500).json({ error: error.message });
}
};
// Controller to handle successful payment
exports.paymentSuccess = async (req, res) => {
const { paymentIntentId, customerId } = req.body;
try {
const paymentIntent = await stripe.paymentIntents.retrieve(paymentIntentId);
const billing = await Billing.create({
customerId,
amount: paymentIntent.amount / 100, // Stripe amount is in cents
paymentIntentId,
status: paymentIntent.status,
});
res.status(201).json({ message: 'Payment recorded successfully', billing });
} catch (error) {
res.status(500).json({ error: error.message });
}
};
// Controller to get billing history
exports.getBillingHistory = async (req, res) => {
try {
const billingHistory = await Billing.findAll({ where: { customerId: req.user.id } });
res.json(billingHistory);
} catch (error) {
res.status(500).json({ error: error.message });
}
};
Explanation:
- createPayment: Creates a payment intent using Stripe.
- req.body: Contains the payment amount.
- paymentSuccess: Handles recording a successful payment.
- req.body: Contains the payment intent ID and customer ID.
- getBillingHistory: Retrieves the billing history for the authenticated customer.
Step 6: Implement the Payment Utility
Create a utility file to handle payment processing using Stripe.
File: utils/payment.js
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
exports.createPaymentIntent = async (amount) => {
try {
const paymentIntent = await stripe.paymentIntents.create({
amount: amount * 100, // Stripe amount is in cents
currency: 'usd',
});
return paymentIntent;
} catch (error) {
throw new Error(error.message);
}
};
Explanation:
- createPaymentIntent: Creates a payment intent using Stripe.
- amount: The amount to be charged.
Step 7: Define Billing Routes
Create a file to define the routes for billing-related operations.
File: routes/billing.js
const express = require('express');
const { createPayment, paymentSuccess, getBillingHistory } = require('../controllers/billing');
const { authMiddleware, roleMiddleware } = require('../middleware/auth');
const billingRouter = express.Router();
// Route to create a payment intent
billingRouter.post('/create-payment', authMiddleware, roleMiddleware('Customer'), createPayment);
// Route to handle successful payment
billingRouter.post('/payment-success', authMiddleware, roleMiddleware('Customer'), paymentSuccess);
// Route to get billing history
billingRouter.get('/billing-history', authMiddleware, roleMiddleware('Customer'), getBillingHistory);
module.exports = billingRouter;
Explanation:
- billingRouter.post(‘/create-payment’): Defines the route to create a payment intent.
- billingRouter.post(‘/payment-success’): Defines the route to handle successful payment.
- billingRouter.get(‘/billing-history’): Defines the route to get billing history.
- authMiddleware, roleMiddleware(‘Customer’): Ensures the routes are accessible only to authenticated customers.
Step 8: Integrate Billing Routes in the Main Server File
Update the server.js
file to include the billing routes.
File: server.js
const express = require('express');
const dotenv = require('dotenv');
const connectDB = require('./config/db');
const authRouter = require('./routes/auth');
const customerRouter = require('./routes/customer');
const ownerRouter = require('./routes/owner');
const adminRouter = require('./routes/admin');
const notificationRouter = require('./routes/notification');
const billingRouter = require('./routes/billing');
const { initModels } = require('./models');
dotenv.config();
const app = express();
app.use(express.json());
app.use('/auth', authRouter);
app.use('/customer', customerRouter);
app.use('/owner', ownerRouter);
app.use('/admin', adminRouter);
app.use('/notification', notificationRouter);
app.use('/billing', billingRouter);
const PORT = process.env.PORT || 3000;
app.listen(PORT, async () => {
console.log(`Server is running on port ${PORT}`);
await initModels();
});
connectDB();
Explanation:
- app.use(‘/billing’, billingRouter): Mounts the billing routes at
/billing
.
By following this detailed structure and code explanation, you can implement a comprehensive Billing System module for the beauty salon booking app. This module provides functionalities to handle billing and payments using Stripe, ensuring a seamless payment experience for customers. The step-by-step explanations help beginners understand each part of the code, its purpose, and how it fits into the larger application.
Module 7: Photo Upload Module
The Photo Upload module provides functionalities for employees to upload photos related to services, which can be viewed by customers. This is particularly useful for sharing before-and-after images or examples of previous work.
Step-by-Step Guide
Step 1: Install Multer and Cloudinary
Multer will handle file uploads, and Cloudinary will store the uploaded images.
npm install multer cloudinary multer-storage-cloudinary
Step 2: Configure Environment Variables
Add Cloudinary-related environment variables to the .env
file.
File: .env
CLOUDINARY_CLOUD_NAME=your_cloudinary_cloud_name
CLOUDINARY_API_KEY=your_cloudinary_api_key
CLOUDINARY_API_SECRET=your_cloudinary_api_secret
Explanation:
- CLOUDINARY_CLOUD_NAME, CLOUDINARY_API_KEY, CLOUDINARY_API_SECRET: Credentials provided by Cloudinary for accessing their API.
Step 3: Define the Photo Model
Create a schema for photos.
File: models/photo.js
const { Sequelize, DataTypes } = require('sequelize');
const sequelize = require('../config/db');
const User = require('./user');
const Service = require('./service');
const Photo = sequelize.define('Photo', {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
url: {
type: DataTypes.STRING,
allowNull: false,
},
description: {
type: DataTypes.STRING,
},
uploadedBy: {
type: DataTypes.INTEGER,
references: {
model: User,
key: 'id',
},
},
serviceId: {
type: DataTypes.INTEGER,
references: {
model: Service,
key: 'id',
},
},
createdAt: {
type: DataTypes.DATE,
defaultValue: DataTypes.NOW,
},
}, {
timestamps: false,
});
module.exports = Photo;
Explanation:
- Photo Schema: Defines the structure of the photo table.
- url, description, uploadedBy, serviceId, createdAt: Fields in the photo schema.
- uploadedBy: References the user who uploaded the photo.
- serviceId: References the service associated with the photo.
Step 4: Configure Cloudinary
Create a utility file to configure Cloudinary.
File: utils/cloudinary.js
const cloudinary = require('cloudinary').v2;
const dotenv = require('dotenv');
dotenv.config();
cloudinary.config({
cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
api_key: process.env.CLOUDINARY_API_KEY,
api_secret: process.env.CLOUDINARY_API_SECRET,
});
module.exports = cloudinary;
Explanation:
- cloudinary.config: Configures Cloudinary using the credentials from the environment variables.
Step 5: Configure Multer Storage
Create a file to configure Multer storage using Cloudinary.
File: config/multer.js
const multer = require('multer');
const { CloudinaryStorage } = require('multer-storage-cloudinary');
const cloudinary = require('../utils/cloudinary');
const storage = new CloudinaryStorage({
cloudinary,
params: {
folder: 'beauty-salon-app',
allowed_formats: ['jpg', 'png'],
},
});
const upload = multer({ storage });
module.exports = upload;
Explanation:
- CloudinaryStorage: Configures Multer to use Cloudinary as the storage destination.
Step 6: Implement the Photo Controller
Create a file to handle the logic for photo-related operations.
File: controllers/photo.js
const Photo = require('../models/photo');
// Controller to upload a photo
exports.uploadPhoto = async (req, res) => {
const { description, serviceId } = req.body;
const { path } = req.file;
try {
const photo = await Photo.create({
url: path,
description,
uploadedBy: req.user.id,
serviceId,
});
res.status(201).json({ message: 'Photo uploaded successfully', photo });
} catch (error) {
res.status(500).json({ error: error.message });
}
};
// Controller to get photos for a service
exports.getServicePhotos = async (req, res) => {
const { serviceId } = req.params;
try {
const photos = await Photo.findAll({ where: { serviceId } });
res.json(photos);
} catch (error) {
res.status(500).json({ error: error.message });
}
};
Explanation:
- uploadPhoto: Handles photo uploads.
- req.body: Contains the photo description and service ID.
- req.file.path: Contains the URL of the uploaded photo.
- getServicePhotos: Retrieves all photos associated with a specific service.
- req.params.serviceId: The ID of the service.
Step 7: Define Photo Routes
Create a file to define the routes for photo-related operations.
File: routes/photo.js
const express = require('express');
const { uploadPhoto, getServicePhotos } = require('../controllers/photo');
const { authMiddleware, roleMiddleware } = require('../middleware/auth');
const upload = require('../config/multer');
const photoRouter = express.Router();
// Route to upload a photo
photoRouter.post('/upload', authMiddleware, roleMiddleware('Owner'), upload.single('photo'), uploadPhoto);
// Route to get photos for a service
photoRouter.get('/service/:serviceId', authMiddleware, getServicePhotos);
module.exports = photoRouter;
Explanation:
- photoRouter.post(‘/upload’): Defines the route to upload a photo.
- photoRouter.get(‘/service/
- authMiddleware, roleMiddleware(‘Owner’): Ensures the upload route is accessible only to authenticated salon owners.
Step 8: Integrate Photo Routes in the Main Server File
Update the server.js
file to include the photo routes.
File: server.js
const express = require('express');
const dotenv = require('dotenv');
const connectDB = require('./config/db');
const authRouter = require('./routes/auth');
const customerRouter = require('./routes/customer');
const ownerRouter = require('./routes/owner');
const adminRouter = require('./routes/admin');
const notificationRouter = require('./routes/notification');
const billingRouter = require('./routes/billing');
const photoRouter = require('./routes/photo');
const { initModels } = require('./models');
dotenv.config();
const app = express();
app.use(express.json());
app.use('/auth', authRouter);
app.use('/customer', customerRouter);
app.use('/owner', ownerRouter);
app.use('/admin', adminRouter);
app.use('/notification', notificationRouter);
app.use('/billing', billingRouter);
app.use('/photo', photoRouter);
const PORT = process.env.PORT || 3000;
app.listen(PORT, async () => {
console.log(`Server is running on port ${PORT}`);
await initModels();
});
connectDB();
Explanation:
- app.use(‘/photo’, photoRouter): Mounts the photo routes at
/photo
.
By following this detailed structure and code explanation, you can implement a comprehensive Photo Upload module for the beauty salon booking app. This module provides functionalities for employees to upload photos related to services, which can be viewed by customers. The step-by-step explanations help beginners understand each part of the code, its purpose, and how it fits into the larger application.
Module 8: Revenue Model Implementation
The Revenue Model Implementation module focuses on integrating various monetization strategies within the beauty salon booking app. This includes prominent listings, commission fees, subscription packages, and advertisements.
Step-by-Step Guide
Step 1: Define the Revenue Model
Create a schema for revenue-related data.
File: models/revenue.js
const { Sequelize, DataTypes } = require('sequelize');
const sequelize = require('../config/db');
const Salon = require('./salon');
const Revenue = sequelize.define('Revenue', {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
salonId: {
type: DataTypes.INTEGER,
references: {
model: Salon,
key: 'id',
},
},
type: {
type: DataTypes.ENUM('Listing', 'Commission', 'Subscription', 'Advertisement'),
allowNull: false,
},
amount: {
type: DataTypes.FLOAT,
allowNull: false,
},
createdAt: {
type: DataTypes.DATE,
defaultValue: DataTypes.NOW,
},
}, {
timestamps: false,
});
module.exports = Revenue;
Explanation:
- revenueSchema: Defines the structure of revenue documents.
- salonId, type, amount, createdAt: Fields in the revenue schema.
- salonId: References the salon associated with the revenue.
Step 2: Update Sequelize Models
Update the index file to initialize all Sequelize models, including the new Revenue model.
File: models/index.js
const sequelize = require('../config/db');
const User = require('./user');
const Service = require('./service');
const Booking = require('./booking');
const Salon = require('./salon');
const Billing = require('./billing');
const Photo = require('./photo');
const Revenue = require('./revenue');
User.hasMany(Salon, { foreignKey: 'ownerId' });
Salon.belongsTo(User, { foreignKey: 'ownerId' });
Salon.hasMany(Service, { foreignKey: 'salonId' });
Service.belongsTo(Salon, { foreignKey: 'salonId' });
Service.hasMany(Booking, { foreignKey: 'serviceId' });
Booking.belongsTo(Service, { foreignKey: 'serviceId' });
User.hasMany(Booking, { foreignKey: 'customerId' });
Booking.belongsTo(User, { foreignKey: 'customerId' });
User.hasMany(Billing, { foreignKey: 'customerId' });
Billing.belongsTo(User, { foreignKey: 'customerId' });
Salon.hasMany(Revenue, { foreignKey: 'salonId' });
Revenue.belongsTo(Salon, { foreignKey: 'salonId' });
const initModels = async () => {
await sequelize.sync({ force: true }); // For development purposes, use { force: true } to drop and recreate tables
console.log('All models were synchronized successfully.');
};
initModels();
module.exports = {
User,
Service,
Booking,
Salon,
Billing,
Photo,
Revenue,
};
Explanation:
- Associations:
- Salon and Revenue: One-to-many relationship where a salon can have multiple revenue records.
Step 3: Implement the Revenue Controller
Create a file to handle the logic for revenue-related operations.
File: controllers/revenue.js
const Revenue = require('../models/revenue');
// Controller to add a new revenue record
exports.addRevenue = async (req, res) => {
const { salonId, type, amount } = req.body;
try {
const revenue = await Revenue.create({
salonId,
type,
amount,
});
res.status(201).json({ message: 'Revenue recorded successfully', revenue });
} catch (error) {
res.status(500).json({ error: error.message });
}
};
// Controller to get revenue records for a salon
exports.getRevenueBySalon = async (req, res) => {
const { salonId } = req.params;
try {
const revenue = await Revenue.findAll({ where: { salonId } });
res.json(revenue);
} catch (error) {
res.status(500).json({ error: error.message });
}
};
// Controller to get all revenue records
exports.getAllRevenue = async (req, res) => {
try {
const revenue = await Revenue.findAll({ include: [Salon] });
res.json(revenue);
} catch (error) {
res.status(500).json({ error: error.message });
}
};
Explanation:
- addRevenue: Handles adding a new revenue record.
- req.body: Contains the revenue data (salonId, type, amount).
- getRevenueBySalon: Retrieves revenue records for a specific salon.
- req.params.salonId: The ID of the salon.
- getAllRevenue: Retrieves all revenue records in the system.
Step 4: Define Revenue Routes
Create a file to define the routes for revenue-related operations.
File: routes/revenue.js
const express = require('express');
const { addRevenue, getRevenueBySalon, getAllRevenue } = require('../controllers/revenue');
const { authMiddleware, roleMiddleware } = require('../middleware/auth');
const revenueRouter = express.Router();
// Route to add a new revenue record
revenueRouter.post('/add', authMiddleware, roleMiddleware('Admin'), addRevenue);
// Route to get revenue records for a salon
revenueRouter.get('/salon/:salonId', authMiddleware, roleMiddleware('Admin'), getRevenueBySalon);
// Route to get all revenue records
revenueRouter.get('/all', authMiddleware, roleMiddleware('Admin'), getAllRevenue);
module.exports = revenueRouter;
Explanation:
- revenueRouter.post(‘/add’): Defines the route to add a new revenue record.
- revenueRouter.get(‘/salon/
- revenueRouter.get(‘/all’): Defines the route to get all revenue records.
- authMiddleware, roleMiddleware(‘Admin’): Ensures the routes are accessible only to authenticated administrators.
Step 5: Integrate Revenue Routes in the Main Server File
Update the server.js
file to include the revenue routes.
File: server.js
const express = require('express');
const dotenv = require('dotenv');
const connectDB = require('./config/db');
const authRouter = require('./routes/auth');
const customerRouter = require('./routes/customer');
const ownerRouter = require('./routes/owner');
const adminRouter = require('./routes/admin');
const notificationRouter = require('./routes/notification');
const billingRouter = require('./routes/billing');
const photoRouter = require('./routes/photo');
const revenueRouter = require('./routes/revenue');
const { initModels } = require('./models');
dotenv.config();
const app = express();
app.use(express.json());
app.use('/auth', authRouter);
app.use('/customer', customerRouter);
app.use('/owner', ownerRouter);
app.use('/admin', adminRouter);
app.use('/notification', notificationRouter);
app.use('/billing', billingRouter);
app.use('/photo', photoRouter);
app.use('/revenue', revenueRouter);
const PORT = process.env.PORT || 3000;
app.listen(PORT, async () => {
console.log(`Server is running on port ${PORT}`);
await initModels();
});
connectDB();
Explanation:
- app.use(‘/revenue’, revenueRouter): Mounts the revenue routes at
/revenue
.
Step 6: Update the Admin Panel to Include Revenue Management
Ensure that the admin panel integrates functionalities to view and manage revenue records.
File: controllers/admin.js (Additions)
const { User, Salon, Booking, Revenue } = require('../models');
// Controller to get all revenue records
exports.getAllRevenue = async (req, res) => {
try {
const revenue = await Revenue.findAll({ include: [Salon] });
res.json(revenue);
} catch (error) {
res.status(500).json({ error: error.message });
}
};
Explanation:
- getAllRevenue: Retrieves all revenue records, populating the salon field with the name.
By following this detailed structure and code explanation, you can implement a comprehensive Revenue Model module for the beauty salon booking app. This module provides functionalities to record and manage different types of revenue, ensuring a seamless and transparent monetization strategy for the platform. The step-by-step explanations help beginners understand each part of the code, its purpose, and how it fits into the larger application
Module 9: Testing and Deployment
The Testing and Deployment module ensures that the beauty salon booking app is thoroughly tested and properly deployed. This includes setting up unit and integration tests, as well as configuring a CI/CD pipeline for continuous integration and deployment.
Step-by-Step Guide
Step 1: Install Testing Libraries
We’ll use Jest and Supertest for testing our application.
npm install jest supertest
Step 2: Configure Jest
Add a Jest configuration to package.json
.
File: package.json (Additions)
"scripts": {
"start": "node server.js",
"test": "jest"
},
"jest": {
"testEnvironment": "node"
}
Step 3: Create Test Files
Create test files for each module.
File: tests/auth.test.js
const request = require('supertest');
const app = require('../server');
describe('Authentication', () => {
it('should register a new user', async () => {
const res = await request(app)
.post('/auth/register')
.send({
username: 'testuser',
email: 'testuser@example.com',
password: 'password',
role: 'Customer',
});
expect(res.statusCode).toEqual(201);
expect(res.body).toHaveProperty('message');
});
it('should login an existing user', async () => {
const res = await request(app)
.post('/auth/login')
.send({
email: 'testuser@example.com',
password: 'password',
});
expect(res.statusCode).toEqual(200);
expect(res.body).toHaveProperty('token');
});
});
Explanation:
- describe: Defines a test suite for the Authentication module.
- it: Defines individual test cases for user registration and login.
File: tests/customer.test.js
const request = require('supertest');
const app = require('../server');
describe('Customer', () => {
let token;
beforeAll(async () => {
const res = await request(app)
.post('/auth/login')
.send({
email: 'testuser@example.com',
password: 'password',
});
token = res.body.token;
});
it('should get all services', async () => {
const res = await request(app)
.get('/customer/services')
.set('Authorization', `Bearer ${token}`);
expect(res.statusCode).toEqual(200);
expect(res.body).toBeInstanceOf(Array);
});
it('should book a service', async () => {
const res = await request(app)
.post('/customer/book')
.set('Authorization', `Bearer ${token}`)
.send({
serviceId: '1', // Replace with a valid service ID
bookingDate: '2023-06-01',
});
expect(res.statusCode).toEqual(201);
expect(res.body).toHaveProperty('message');
});
});
Explanation:
- beforeAll: Runs before all tests in the suite to obtain a token for authentication.
- describe: Defines a test suite for the Customer module.
- it: Defines individual test cases for getting services and booking a service.
Step 4: Continuous Integration and Deployment
Set up a CI/CD pipeline using GitHub Actions. Create a .github/workflows/ci.yml
file.
File: .github/workflows/ci.yml
name: CI/CD Pipeline
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Node.js
uses: actions/setup-node@v2
with:
node-version: '14'
- name: Install dependencies
run: npm install
- name: Run tests
run: npm test
Explanation:
- on: Specifies the events that trigger the workflow (push and pull requests to the main branch).
- jobs: Defines the CI job to build and test the application.
- steps: Lists the steps to perform in the CI job, including checking out the code, setting up Node.js, installing dependencies, and running tests.
Step 5: Deployment
We’ll use Heroku for deployment. Follow these steps to deploy the app:
Install the Heroku CLI: Follow the instructions on the Heroku CLI page.
Login to Heroku:
heroku login
Create a new Heroku app:
heroku create beauty-salon-app
Deploy the app:
git push heroku main
Set environment variables:
heroku config:set PORT=3000 MONGO_URI=your_mongo_uri JWT_SECRET=your_jwt_secret_key TWILIO_ACCOUNT_SID=your_twilio_account_sid TWILIO_AUTH_TOKEN=your_twilio_auth_token TWILIO_PHONE_NUMBER=your_twilio_phone_number STRIPE_SECRET_KEY=your_stripe_secret_key CLOUDINARY_CLOUD_NAME=your_cloudinary_cloud_name CLOUDINARY_API_KEY=your_cloudinary_api_key CLOUDINARY_API_SECRET=your_cloudinary_api_secret
Explanation:
- Heroku CLI: Used to interact with Heroku from the command line.
- heroku create: Creates a new Heroku app.
- git push heroku main: Deploys the code to Heroku.
- heroku config:set: Sets the environment variables required by the app.
Step 6: Update the Main Server File
Ensure that the server.js
file exports the app for testing purposes.
File: server.js
const express = require('express');
const dotenv = require('dotenv');
const connectDB = require('./config/db');
const authRouter = require('./routes/auth');
const customerRouter = require('./routes/customer');
const ownerRouter = require('./routes/owner');
const adminRouter = require('./routes/admin');
const notificationRouter = require('./routes/notification');
const billingRouter = require('./routes/billing');
const photoRouter = require('./routes/photo');
const revenueRouter = require('./routes/revenue');
const { initModels } = require('./models');
dotenv.config();
const app = express();
app.use(express.json());
app.use('/auth', authRouter);
app.use('/customer', customerRouter);
app.use('/owner', ownerRouter);
app.use('/admin', adminRouter);
app.use('/notification', notificationRouter);
app.use('/billing', billingRouter);
app.use('/photo', photoRouter);
app.use('/revenue', revenueRouter);
const PORT = process.env.PORT || 3000;
app.listen(PORT, async () => {
console.log(`Server is running on port ${PORT}`);
await initModels();
});
connectDB();
module.exports = app; // Export the app for testing
Explanation:
- module.exports = app: Exports the app for testing purposes.
By following this detailed structure and code explanation, you can implement a comprehensive Testing and Deployment module for the beauty salon booking app. This module ensures that the application is thoroughly tested and properly deployed using CI/CD pipelines and platforms like Heroku. The step-by-step explanations help beginners understand each part of the code, its purpose, and how it fits into the larger application.
Module 10: Analytics and Reporting
The Analytics and Reporting module provides functionalities for administrators to gain insights into the app’s usage. This includes generating reports on user activities, revenue, bookings, and other key metrics.
Step 0: Install Chart Libraries (Optional for Graphs)
If you want to generate visual reports, you might consider using a library like chart.js
or d3.js
. However, for server-side data aggregation, we don’t need these.
Step 1: Implement the Analytics Controller
Create a file to handle the logic for analytics and reporting-related operations.
File: controllers/analytics.js
const { User, Booking, Revenue, Salon } = require('../models');
// Controller to get user activity report
exports.getUserActivityReport = async (req, res) => {
try {
const userCount = await User.count();
const bookingCount = await Booking.count();
const revenueCount = await Revenue.count();
res.json({
userCount,
bookingCount,
revenueCount,
});
} catch (error) {
res.status(500).json({ error: error.message });
}
};
// Controller to get revenue report
exports.getRevenueReport = async (req, res) => {
try {
const revenue = await Revenue.findAll({
attributes: ['type', [sequelize.fn('sum', sequelize.col('amount')), 'totalAmount']],
group: ['type'],
});
res.json(revenue);
} catch (error) {
res.status(500).json({ error: error.message });
}
};
// Controller to get booking report
exports.getBookingReport = async (req, res) => {
try {
const bookings = await Booking.findAll({
attributes: [
[sequelize.fn('date_format', sequelize.col('bookingDate'), '%Y-%m'), 'month'],
[sequelize.fn('count', sequelize.col('id')), 'totalBookings'],
],
group: ['month'],
order: [['month', 'ASC']],
});
res.json(bookings);
} catch (error) {
res.status(500).json({ error: error.message });
}
};
// Controller to get salon report
exports.getSalonReport = async (req, res) => {
try {
const salonCount = await Salon.count();
const activeSalons = await Salon.count({ where: { status: 'Active' } });
res.json({
salonCount,
activeSalons,
});
} catch (error) {
res.status(500).json({ error: error.message });
}
};
Explanation:
- getUserActivityReport: Retrieves a report on user activities including the count of users, bookings, and revenue records.
- getRevenueReport: Retrieves a report on revenue grouped by type.
- getBookingReport: Retrieves a report on bookings grouped by month.
- getSalonReport: Retrieves a report on the number of salons and active salons.
Step 3: Define Analytics Routes
Create a file to define the routes for analytics and reporting-related operations.
File: routes/analytics.js
const express = require('express');
const { getUserActivityReport, getRevenueReport, getBookingReport, getSalonReport } = require('../controllers/analytics');
const { authMiddleware, roleMiddleware } = require('../middleware/auth');
const analyticsRouter = express.Router();
// Route to get user activity report
analyticsRouter.get('/user-activity', authMiddleware, roleMiddleware('Admin'), getUserActivityReport);
// Route to get revenue report
analyticsRouter.get('/revenue', authMiddleware, roleMiddleware('Admin'), getRevenueReport);
// Route to get booking report
analyticsRouter.get('/bookings', authMiddleware, roleMiddleware('Admin'), getBookingReport);
// Route to get salon report
analyticsRouter.get('/salons', authMiddleware, roleMiddleware('Admin'), getSalonReport);
module.exports = analyticsRouter;
Explanation:
- analyticsRouter.get(‘/user-activity’): Defines the route to get a user activity report.
- analyticsRouter.get(‘/revenue’): Defines the route to get a revenue report.
- analyticsRouter.get(‘/bookings’): Defines the route to get a booking report.
- analyticsRouter.get(‘/salons’): Defines the route to get a salon report.
- authMiddleware, roleMiddleware(‘Admin’): Ensures the routes are accessible only to authenticated administrators.
Step 4: Integrate Analytics Routes in the Main Server File
Update the server.js
file to include the analytics routes.
File: server.js
const express = require('express');
const dotenv = require('dotenv');
const connectDB = require('./config/db');
const authRouter = require('./routes/auth');
const customerRouter = require('./routes/customer');
const ownerRouter = require('./routes/owner');
const adminRouter = require('./routes/admin');
const notificationRouter = require('./routes/notification');
const billingRouter = require('./routes/billing');
const photoRouter = require('./routes/photo');
const revenueRouter = require('./routes/revenue');
const analyticsRouter = require('./routes/analytics');
const { initModels } = require('./models');
dotenv.config();
const app = express();
app.use(express.json());
app.use('/auth', authRouter);
app.use('/customer', customerRouter);
app.use('/owner', ownerRouter);
app.use('/admin', adminRouter);
app.use('/notification', notificationRouter);
app.use('/billing', billingRouter);
app.use('/photo', photoRouter);
app.use('/revenue', revenueRouter);
app.use('/analytics', analyticsRouter);
const PORT = process.env.PORT || 3000;
app.listen(PORT, async () => {
console.log(`Server is running on port ${PORT}`);
await initModels();
});
connectDB();
Explanation:
- app.use(‘/analytics’, analyticsRouter): Mounts the analytics routes at
/analytics
.
Step 5: Add Tests for Analytics
Create a test file for analytics-related operations.
File: tests/analytics.test.js
const request = require('supertest');
const app = require('../server');
describe('Analytics', () => {
let token;
beforeAll(async () => {
const res = await request(app)
.post('/auth/login')
.send({
email: 'admin@example.com',
password: 'adminpassword',
});
token = res.body.token;
});
it('should get user activity report', async () => {
const res = await request(app)
.get('/analytics/user-activity')
.set('Authorization', `Bearer ${token}`);
expect(res.statusCode).toEqual(200);
expect(res.body).toHaveProperty('userCount');
expect(res.body).toHaveProperty('bookingCount');
expect(res.body).toHaveProperty('revenueCount');
});
it('should get revenue report', async () => {
const res = await request(app)
.get('/analytics/revenue')
.set('Authorization', `Bearer ${token}`);
expect(res.statusCode).toEqual(200);
expect(res.body).toBeInstanceOf(Array);
});
it('should get booking report', async () => {
const res = await request(app)
.get('/analytics/bookings')
.set('Authorization', `Bearer ${token}`);
expect(res.statusCode).toEqual(200);
expect(res.body).toBeInstanceOf(Array);
});
it('should get salon report', async () => {
const res = await request(app)
.get('/analytics/salons')
.set('Authorization', `Bearer ${token}`);
expect(res.statusCode).toEqual(200);
expect(res.body).toHaveProperty('salonCount');
expect(res.body).toHaveProperty('activeSalons');
});
});
Explanation:
- Tests: Includes tests for each analytics endpoint to ensure they are returning the expected results.
By following this detailed structure and code explanation, you can implement a comprehensive Analytics and Reporting module for the beauty salon booking app. This module provides functionalities for generating reports on user activities, revenue, bookings, and salon statuses, giving administrators valuable insights into the app’s usage. The step-by-step explanations help beginners understand each part of the code, its purpose, and how it fits into the larger application.
Welcome to DevTechTutor.com, your ultimate resource for mastering web development and technology! Whether you're a beginner eager to dive into coding or an experienced developer looking to sharpen your skills, DevTechTutor.com is here to guide you every step of the way. Our mission is to make learning web development accessible, engaging, and effective.