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.
Project Structure
beauty-salon-app/
│
├── .env
├── package.json
├── server.js
├── config/
│ └── db.js
├── controllers/
│ └── auth.js
├── middleware/
│ └── auth.js
├── models/
│ └── User.js
└── routes/
└── auth.js
File: .env
The .env
file contains environment variables:
PORT=3000
MONGO_URI=mongodb://localhost:27017/beauty_salon_app
JWT_SECRET=your_jwt_secret_key
- PORT: The port number where the server will run.
- MONGO_URI: The connection string for MongoDB.
- JWT_SECRET: A secret key used to sign JWT tokens for security.
File: package.json
Ensure package.json
includes necessary dependencies:
{
"name": "beauty-salon-app",
"version": "1.0.0",
"main": "server.js",
"dependencies": {
"bcrypt": "^5.0.1",
"body-parser": "^1.19.0",
"dotenv": "^10.0.0",
"express": "^4.17.1",
"jsonwebtoken": "^8.5.1",
"mongoose": "^5.12.3"
},
"scripts": {
"start": "node server.js"
}
}
- name, version, main: Basic project information.
- dependencies: Lists the required libraries (express, mongoose, bcrypt, jsonwebtoken, dotenv, body-parser).
- scripts: Defines a start script to run the server.
Module 1: User Authentication and Authorization
Purpose:
The User Authentication and Authorization module is the backbone of the beauty salon booking app. It ensures that users, salon owners, and administrators can securely access the system and perform actions based on their roles. This module manages the registration, login, and role-based access control, ensuring that only authorized users can access specific parts of the app.
Key Features:
User Registration:
- Description: Allows users to create accounts with different roles (Customer, Owner, Admin).
- Endpoints:
POST /register
: Registers a new user with role-specific information.
Authentication:
- Description: Handles user login, password management, and token generation.
- Endpoints:
POST /login
: Authenticates a user and returns a JWT token.POST /logout
: Invalidates the current user’s session (optional if using stateless JWT).POST /password-reset-request
: Sends a password reset link to the user’s email.POST /password-reset
: Resets the user’s password using a token from the reset link.
Authorization:
- Description: Implements role-based access control to secure endpoints.
- Middleware:
authMiddleware
: Verifies the JWT token and extracts user information.roleMiddleware
: Checks the user’s role to authorize access to specific endpoints.
Technologies:
- JWT (JSON Web Tokens): For token-based authentication.
- bcrypt: For hashing and verifying passwords.
- Express.js: Web framework for building the API.
- MongoDB: Database for storing user information.
- Mongoose: ODM for MongoDB.
Step-by-Step Guide
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
2. Initialize a new Node.js project:
npm init -y
3. Install the necessary dependencies:
npm install express mongoose bcrypt jsonwebtoken dotenv body-parser
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
├── controllers/
│ └── auth.js
├── middleware/
│ └── auth.js
├── models/
│ └── User.js
└── routes/
└── auth.js
Step 3: Create Environment Variables
Create a file named .env
in the root directory to store environment variables. This file contains configuration values that should not be hard-coded into the application.
File: .env
PORT=3000
MONGO_URI=mongodb://localhost:27017/beauty_salon_app
JWT_SECRET=your_jwt_secret_key
- PORT: The port number where the server will run.
- MONGO_URI: The connection string for MongoDB.
- JWT_SECRET: A secret key used to sign JWT tokens for security.
Step 4: Configure package.json
Ensure package.json
includes the necessary dependencies and scripts:
File: package.json
Answers to Exercises
1.
class Person {
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
getFullName() {
return `${this.firstName} ${this.lastName}`;
}
}
2.
class Circle {
constructor(radius) {
this.radius = radius;
}
getArea() {
return Math.PI * this.radius * this.radius;
}
}
3.
class Employee {
constructor(name, position) {
this.name = name;
this.position = position;
}
describe() {
return `${this.name} is a ${this.position}`;
}
}
4.
class Rectangle {
constructor(width, height) {
this.width = width;
this.height = height;
}
getPerimeter() {
return 2 * (this.width + this.height);
}
}
5.
class Book {
constructor(title, author) {
this.title = title;
this.author = author;
}
getSummary() {
return `${this.title} by ${this.author}`;
}
}
6.
class Dog {
constructor(name, breed) {
this.name = name;
this.breed = breed;
}
bark() {
return 'Woof!';
}
}
7.
class BankAccount {
constructor(accountNumber, balance) {
this.accountNumber = accountNumber;
this.balance = balance;
}
deposit(amount) {
this.balance += amount;
}
}
8.
class Student {
constructor(name, grades) {
this.name = name;
this.grades = grades;
}
getAverageGrade() {
const total = this.grades.reduce((sum, grade) => sum + grade, 0);
return total / this.grades.length;
}
}
9.
class Product {
constructor(name, price) {
this.name = name;
this.price = price;
}
applyDiscount(percentage) {
this.price -= (this.price * percentage) / 100;
}
}
10.
class Movie {
constructor(title, director) {
this.title = title;
this.director = director;
}
getDetails() {
return `${this.title} directed by ${this.director}`;
}
}
Additional Projects Covering Previous Days
Project: Personal Budget Tracker
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut elit tellus, luctus nec ullamcorper mattis, pulvinar dapibus leo.
beauty-salon-app/
│
├── .env
├── package.json
├── server.js
├── config/
│ └── db.js
├── controllers/
│ └── auth.js
│ └── customer.js
│ └── owner.js
│ └── admin.js
│ └── notification.js
│ └── billing.js
│ └── photo.js
├── middleware/
│ └── auth.js
├── models/
│ └── User.js
│ └── Service.js
│ └── Booking.js
│ └── Salon.js
│ └── Billing.js
│ └── Photo.js
├── routes/
│ └── auth.js
│ └── customer.js
│ └── owner.js
│ └── admin.js
│ └── notification.js
│ └── billing.js
│ └── photo.js
└── utils/
└── payment.js
└── sms.js
└── cloudinary.js
Answers to Exercises
1.
class Person {
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
getFullName() {
return `${this.firstName} ${this.lastName}`;
}
}
2.
class Circle {
constructor(radius) {
this.radius = radius;
}
getArea() {
return Math.PI * this.radius * this.radius;
}
}
3.
class Employee {
constructor(name, position) {
this.name = name;
this.position = position;
}
describe() {
return `${this.name} is a ${this.position}`;
}
}
4.
class Rectangle {
constructor(width, height) {
this.width = width;
this.height = height;
}
getPerimeter() {
return 2 * (this.width + this.height);
}
}
5.
class Book {
constructor(title, author) {
this.title = title;
this.author = author;
}
getSummary() {
return `${this.title} by ${this.author}`;
}
}
6.
class Dog {
constructor(name, breed) {
this.name = name;
this.breed = breed;
}
bark() {
return 'Woof!';
}
}
7.
class BankAccount {
constructor(accountNumber, balance) {
this.accountNumber = accountNumber;
this.balance = balance;
}
deposit(amount) {
this.balance += amount;
}
}
8.
class Student {
constructor(name, grades) {
this.name = name;
this.grades = grades;
}
getAverageGrade() {
const total = this.grades.reduce((sum, grade) => sum + grade, 0);
return total / this.grades.length;
}
}
9.
class Product {
constructor(name, price) {
this.name = name;
this.price = price;
}
applyDiscount(percentage) {
this.price -= (this.price * percentage) / 100;
}
}
10.
class Movie {
constructor(title, director) {
this.title = title;
this.director = director;
}
getDetails() {
return `${this.title} directed by ${this.director}`;
}
}
Additional Projects Covering Previous Days
Project: Personal Budget Tracker
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut elit tellus, luctus nec ullamcorper mattis, pulvinar dapibus leo.
{
"name": "beauty-salon-app",
"version": "1.0.0",
"main": "server.js",
"dependencies": {
"bcrypt": "^5.0.1",
"body-parser": "^1.19.0",
"dotenv": "^10.0.0",
"express": "^4.17.1",
"jsonwebtoken": "^8.5.1",
"mongoose": "^5.12.3"
},
"scripts": {
"start": "node server.js"
}
}
- name, version, main: Basic project information.
- dependencies: Lists the required libraries (express, mongoose, bcrypt, jsonwebtoken, dotenv, body-parser).
- scripts: Defines a start script to run the server.
Step 5: Initialize the Express Application
Create the main server file to initialize the Express application and connect to MongoDB.
File: server.js
const express = require('express');
const dotenv = require('dotenv');
const connectDB = require('./config/db');
const authRouter = require('./routes/auth');
// Load environment variables from .env file
dotenv.config();
// Initialize the Express application
const app = express();
// Middleware to parse JSON request bodies
app.use(express.json());
// Mount the authentication routes at /auth
app.use('/auth', authRouter);
// Define the port the server will listen on
const PORT = process.env.PORT || 3000;
// Start the server and listen on the specified port
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
// Connect to MongoDB
connectDB();
- dotenv.config(): Loads environment variables from the
.env
file. - express.json(): Middleware to parse JSON request bodies.
- app.use(‘/auth’, authRouter): Mounts the authentication routes.
- connectDB(): Connects to the MongoDB database.
Step 6: Configure MongoDB Connection
Create a file to establish a connection to MongoDB using Mongoose.
File: config/db.js
const mongoose = require('mongoose');
const dotenv = require('dotenv');
// Load environment variables from .env file
dotenv.config();
// Function to connect to MongoDB
const connectDB = async () => {
try {
await mongoose.connect(process.env.MONGO_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
console.log('Connected to MongoDB');
} catch (error) {
console.error('Error connecting to MongoDB:', error);
process.exit(1); // Exit process with failure
}
};
module.exports = connectDB;
- dotenv.config(): Loads environment variables.
- mongoose.connect(): Connects to MongoDB using the connection string from the
.env
file. - async/await: Ensures that the database connection is established before proceeding.
Step 7: Define the User Schema and Model
Create a file to define the User schema and model using Mongoose. This is the blueprint for the user documents stored in MongoDB.
File: models/User.js
const mongoose = require('mongoose');
const bcrypt = require('bcrypt');
// Define the user schema
const userSchema = new mongoose.Schema({
username: { type: String, required: true, unique: true },
email: { type: String, required: true, unique: true },
password: { type: String, required: true },
role: { type: String, enum: ['Customer', 'Owner', 'Admin'], required: true },
});
// Pre-save middleware to hash the password before saving
userSchema.pre('save', async function (next) {
if (this.isModified('password') || this.isNew) {
this.password = await bcrypt.hash(this.password, 10);
}
next();
});
// Create the User model
const User = mongoose.model('User', userSchema);
module.exports = User;
- userSchema: Defines the structure of user documents.
- username, email, password, role: Fields in the user schema.
- enum: Restricts the role field to specific values.
- pre(‘save’) middleware: Hashes the password before saving the user document if the password field is new or modified.
- User model: Represents the user collection in MongoDB.
Step 8: Implement the Authentication Controller
Create a file to handle the logic for user registration and login. Controllers contain the business logic for our routes.
File: controllers/auth.js
const User = require('../models/User');
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
// Controller for user registration
exports.register = async (req, res) => {
const { username, email, password, role } = req.body;
try {
const newUser = new User({ username, email, password, role });
await newUser.save();
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({ 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 });
}
};
- register: Handles user registration by creating a new user and saving it to the database.
- req.body: Contains the incoming user data.
- new User: Creates a new user document.
- save(): Saves the user document to the database.
- res.status(201).json(): Returns a success message with a 201 status code.
- login: Handles user login by verifying credentials and generating a JWT token upon successful authentication.
- findOne({ email }): Finds a user by email.
- bcrypt.compare(): Compares the provided password with the stored hashed password.
- jwt.sign(): Generates a JWT token containing the user’s ID and role, signed with the secret key.
Step 9: Create Middleware for Authentication and Authorization
Create a file to define middleware functions for authentication and authorization. Middleware functions have access to the request object (req
), the response object (res
), and the next middleware function in the application’s request-response cycle.
File: middleware/auth.js
const jwt = require('jsonwebtoken');
// Middleware to verify JWT token
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' });
}
};
// Middleware to check user role
const roleMiddleware = (requiredRole) => (req, res, next) => {
if (req.user.role !== requiredRole) {
return res.status(403).json({ error: 'Access denied' });
}
next();
};
module.exports = { authMiddleware, roleMiddleware };
- 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 10: Define the Authentication Routes
Create a file to define the authentication routes. Routes are the endpoints that respond to client requests.
File: routes/auth.js
const express = require('express');
const { register, login } = require('../controllers/auth');
const authRouter = express.Router();
// Route for user registration
authRouter.post('/register', register);
// Route for user login
authRouter.post('/login', login);
module.exports = authRouter;
- authRouter.post(‘/register’): Defines the route for user registration.
- authRouter.post(‘/login’): Defines the route for user login.
By following this detailed structure and code explanation, you can implement a robust User Authentication and Authorization module for the beauty salon booking app. This module ensures secure access by managing user registration, login, and role-based access control. The step-by-step explanations help beginners understand each part of the code, its purpose, and how it fits into the larger application.
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.
Project Structure
Let’s add the new files and folders to the existing project structure:
beauty-salon-app/
│
├── .env
├── package.json
├── server.js
├── config/
│ └── db.js
├── controllers/
│ └── auth.js
│ └── customer.js
├── middleware/
│ └── auth.js
├── models/
│ └── User.js
│ └── Service.js
│ └── Booking.js
├── routes/
│ └── auth.js
│ └── customer.js
└── utils/
└── payment.js
Step 1: Define the Customer-Related Models
Create schemas for services and bookings.
File: models/Service.js
const mongoose = require('mongoose');
// Define the service schema
const serviceSchema = new mongoose.Schema({
name: { type: String, required: true },
description: { type: String, required: true },
price: { type: Number, required: true },
duration: { type: Number, required: true }, // Duration in minutes
createdBy: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true }, // Reference to the salon owner
});
const Service = mongoose.model('Service', serviceSchema);
module.exports = Service;
- serviceSchema: Defines the structure of service documents.
- name, description, price, duration, createdBy: Fields in the service schema.
- createdBy: References the user (salon owner) who created the service.
File: models/Booking.js
const mongoose = require('mongoose');
// Define the booking schema
const bookingSchema = new mongoose.Schema({
service: { type: mongoose.Schema.Types.ObjectId, ref: 'Service', required: true },
customer: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
bookingDate: { type: Date, required: true },
status: { type: String, enum: ['Pending', 'Confirmed', 'Cancelled'], default: 'Pending' },
});
const Booking = mongoose.model('Booking', bookingSchema);
module.exports = Booking;
- bookingSchema: Defines the structure of booking documents.
- service, customer, bookingDate, status: Fields in the booking schema.
- service, customer: References the service and customer associated with the booking.
Step 2: Implement the Customer Controller
Create a file to handle the logic for customer-related operations.
File: controllers/customer.js
const Service = require('../models/Service');
const Booking = require('../models/Booking');
// Controller to get all services
exports.getAllServices = async (req, res) => {
try {
const services = await Service.find().populate('createdBy', 'username');
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 = new Booking({
service: serviceId,
customer: req.user.id,
bookingDate,
});
await booking.save();
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.find({ customer: req.user.id }).populate('service');
res.json(bookings);
} catch (error) {
res.status(500).json({ error: error.message });
}
};
- getAllServices: Retrieves all available services.
- bookService: Handles booking a service.
- req.body: Contains the incoming booking data (serviceId, bookingDate).
- req.user.id: The ID of the currently authenticated user (customer).
- getCustomerBookings: Retrieves bookings for the currently authenticated customer.
Step 3: Create Middleware for Authentication
Ensure the existing authentication middleware is applied to customer routes.
File: middleware/auth.js
const jwt = require('jsonwebtoken');
// Middleware to verify JWT token
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' });
}
};
// Middleware to check user role
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 4: 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 5: 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');
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, () => {
console.log(`Server is running on port ${PORT}`);
});
connectDB();
- app.use(‘/customer’, customerRouter): Mounts the customer routes at
/customer
.
Step 6: 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. 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.
Project Structure
We’ll add new files and folders to the existing project structure:
beauty-salon-app/
│
├── .env
├── package.json
├── server.js
├── config/
│ └── db.js
├── controllers/
│ └── auth.js
│ └── customer.js
│ └── owner.js
├── middleware/
│ └── auth.js
├── models/
│ └── User.js
│ └── Service.js
│ └── Booking.js
│ └── Salon.js
├── routes/
│ └── auth.js
│ └── customer.js
│ └── owner.js
└── utils/
└── payment.js
Step 1: Define the Owner-Related Models
Create a schema for salons.
File: models/Salon.js
const mongoose = require('mongoose');
// Define the salon schema
const salonSchema = new mongoose.Schema({
name: { type: String, required: true },
address: { type: String, required: true },
phone: { type: String, required: true },
owner: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
services: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Service' }],
});
const Salon = mongoose.model('Salon', salonSchema);
module.exports = Salon;
- salonSchema: Defines the structure of salon documents.
- name, address, phone, owner, services: Fields in the salon schema.
- owner: References the user (salon owner) who owns the salon.
- services: References the services offered by the salon.
Step 2: Implement the Owner Controller
Create a file to handle the logic for owner-related operations.
File: controllers/owner.js
const Salon = require('../models/Salon');
const Service = require('../models/Service');
const Booking = require('../models/Booking');
// Controller to register a new salon
exports.registerSalon = async (req, res) => {
const { name, address, phone } = req.body;
try {
const newSalon = new Salon({
name,
address,
phone,
owner: req.user.id,
});
await newSalon.save();
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 = new Service({
name,
description,
price,
duration,
createdBy: req.user.id,
});
const service = await newService.save();
const salon = await Salon.findById(salonId);
salon.services.push(service._id);
await salon.save();
res.status(201).json({ message: 'Service added successfully', service });
} catch (error) {
res.status(500).json({ error: error.message });
}
};
// Controller to get all services of a salon
exports.getSalonServices = async (req, res) => {
try {
const salon = await Salon.findById(req.params.salonId).populate('services');
res.json(salon.services);
} catch (error) {
res.status(500).json({ error: error.message });
}
};
// Controller to manage appointments
exports.getAppointments = async (req, res) => {
try {
const appointments = await Booking.find({ service: { $in: req.user.services } })
.populate('service')
.populate('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.find({ service: { $in: req.user.services } })
.populate('service')
.populate('customer');
res.json(history);
} catch (error) {
res.status(500).json({ error: error.message });
}
};
- registerSalon: Handles the registration of a new salon.
- req.body: Contains the incoming salon data (name, address, phone).
- req.user.id: The ID of the currently authenticated user (salon owner).
- addService: Handles adding a new service to a salon.
- req.body: Contains the incoming service data (name, description, price, duration, salonId).
- req.user.id: The ID of the currently authenticated user (salon owner).
- getSalonServices: Retrieves all services of a specific salon.
- getAppointments: Retrieves all appointments for the services provided by the authenticated salon owner.
- getServiceHistory: Retrieves the service history for the services provided by the authenticated salon owner.
Step 3: Create Middleware for Authentication
Ensure the existing authentication middleware is applied to owner routes.
File: middleware/auth.js
const jwt = require('jsonwebtoken');
// Middleware to verify JWT token
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' });
}
};
// Middleware to check user role
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 4: 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', authMiddleware, roleMiddleware('Owner'), getAppointments);
// Route to get service history
ownerRouter.get('/service-history', authMiddleware, roleMiddleware('Owner'), getServiceHistory);
module.exports = ownerRouter;
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.
Project Structure
We’ll add new files and folders to the existing project structure:
beauty-salon-app/
│
├── .env
├── package.json
├── server.js
├── config/
│ └── db.js
├── controllers/
│ └── auth.js
│ └── customer.js
│ └── owner.js
├── middleware/
│ └── auth.js
├── models/
│ └── User.js
│ └── Service.js
│ └── Booking.js
│ └── Salon.js
├── routes/
│ └── auth.js
│ └── customer.js
│ └── owner.js
└── utils/
└── payment.js
Step 1: Define the Owner-Related Models
Create a schema for salons.
File: models/Salon.js
const mongoose = require('mongoose');
// Define the salon schema
const salonSchema = new mongoose.Schema({
name: { type: String, required: true },
address: { type: String, required: true },
phone: { type: String, required: true },
owner: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
services: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Service' }],
});
const Salon = mongoose.model('Salon', salonSchema);
module.exports = Salon;
- salonSchema: Defines the structure of salon documents.
- name, address, phone, owner, services: Fields in the salon schema.
- owner: References the user (salon owner) who owns the salon.
- services: References the services offered by the salon.
Step 2: Implement the Owner Controller
Create a file to handle the logic for owner-related operations.
File: controllers/owner.js
const Salon = require('../models/Salon');
const Service = require('../models/Service');
const Booking = require('../models/Booking');
// Controller to register a new salon
exports.registerSalon = async (req, res) => {
const { name, address, phone } = req.body;
try {
const newSalon = new Salon({
name,
address,
phone,
owner: req.user.id,
});
await newSalon.save();
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 = new Service({
name,
description,
price,
duration,
createdBy: req.user.id,
});
const service = await newService.save();
const salon = await Salon.findById(salonId);
salon.services.push(service._id);
await salon.save();
res.status(201).json({ message: 'Service added successfully', service });
} catch (error) {
res.status(500).json({ error: error.message });
}
};
// Controller to get all services of a salon
exports.getSalonServices = async (req, res) => {
try {
const salon = await Salon.findById(req.params.salonId).populate('services');
res.json(salon.services);
} catch (error) {
res.status(500).json({ error: error.message });
}
};
// Controller to manage appointments
exports.getAppointments = async (req, res) => {
try {
const appointments = await Booking.find({ service: { $in: req.user.services } })
.populate('service')
.populate('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.find({ service: { $in: req.user.services } })
.populate('service')
.populate('customer');
res.json(history);
} catch (error) {
res.status(500).json({ error: error.message });
}
};
- registerSalon: Handles the registration of a new salon.
- req.body: Contains the incoming salon data (name, address, phone).
- req.user.id: The ID of the currently authenticated user (salon owner).
- addService: Handles adding a new service to a salon.
- req.body: Contains the incoming service data (name, description, price, duration, salonId).
- req.user.id: The ID of the currently authenticated user (salon owner).
- getSalonServices: Retrieves all services of a specific salon.
- getAppointments: Retrieves all appointments for the services provided by the authenticated salon owner.
- getServiceHistory: Retrieves the service history for the services provided by the authenticated salon owner.
Step 3: Create Middleware for Authentication
Ensure the existing authentication middleware is applied to owner routes.
File: middleware/auth.js
const jwt = require('jsonwebtoken');
// Middleware to verify JWT token
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' });
}
};
// Middleware to check user role
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 4: 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', authMiddleware, roleMiddleware('Owner'), getAppointments);
// Route to get service history
ownerRouter.get('/service-history', authMiddleware, roleMiddleware('Owner'), getServiceHistory);
module.exports = ownerRouter;
- 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’): Defines the route to manage appointments.
- ownerRouter.get(‘/service-history’): Defines the route to get service history.
- authMiddleware, roleMiddleware(‘Owner’): Ensures the routes are accessible only to authenticated salon owners.
Step 5: Integrate Owner Routes in the Main Server File
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');
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, () => {
console.log(`Server is running on port ${PORT}`);
});
connectDB();
- 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. 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.
Project Structure
We’ll add new files and folders to the existing project structure:
beauty-salon-app/
│
├── .env
├── package.json
├── server.js
├── config/
│ └── db.js
├── controllers/
│ └── auth.js
│ └── customer.js
│ └── owner.js
│ └── admin.js
├── middleware/
│ └── auth.js
├── models/
│ └── User.js
│ └── Service.js
│ └── Booking.js
│ └── Salon.js
├── routes/
│ └── auth.js
│ └── customer.js
│ └── owner.js
│ └── admin.js
└── utils/
└── payment.js
Step 1: Implement the Admin Controller
Create a file to handle the logic for admin-related operations.
File: controllers/admin.js
const User = require('../models/User');
const Salon = require('../models/Salon');
const Booking = require('../models/Booking');
// Controller to get all users
exports.getAllUsers = async (req, res) => {
try {
const users = await User.find();
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.find().populate('owner', 'username email');
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.findById(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.find().populate('service 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 (erro
- getAllUsers: Retrieves all users in the system.
- getAllSalons: Retrieves all salons in the system.
- approveSalon: Approves or disapproves a salon.
- req.body: Contains the incoming approval data (salonId, approved).
- getAllBookings: Retrieves all bookings in the system.
- managePayments: Placeholder for managing payments.
Step 2: Create Middleware for Authentication
Ensure the existing authentication middleware is applied to admin routes.
File: middleware/auth.js
const jwt = require('jsonwebtoken');
// Middleware to verify JWT token
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' });
}
};
// Middleware to check user role
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 3: 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;
- 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 4: 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');
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, () => {
console.log(`Server is running on port ${PORT}`);
});
connectDB();
- 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. 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.
Project Structure
We’ll add new files and folders to the existing project structure:
beauty-salon-app/
│
├── .env
├── package.json
├── server.js
├── config/
│ └── db.js
├── controllers/
│ └── auth.js
│ └── customer.js
│ └── owner.js
│ └── admin.js
│ └── notification.js
├── middleware/
│ └── auth.js
├── models/
│ └── User.js
│ └── Service.js
│ └── Booking.js
│ └── Salon.js
├── routes/
│ └── auth.js
│ └── customer.js
│ └── owner.js
│ └── admin.js
│ └── notification.js
└── utils/
└── payment.js
└── sms.js
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
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 { 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.findById(bookingId).populate('customer');
if (!booking) {
return res.status(404).json({ error: 'Booking not found' });
}
// Send SMS notification
const message = `Dear ${booking.customer.username}, your booking for ${booking.bookingDate} has been confirmed.`;
await sendSMS(booking.customer.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.findById(bookingId).populate('customer');
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.customer.username}, you have a booking scheduled for tomorrow at ${bookingDate.toLocaleTimeString()}.`;
await sendSMS(booking.customer.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 });
}
};
- 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');
}
};
- 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;
- 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');
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, () => {
console.log(`Server is running on port ${PORT}`);
});
connectDB();
- 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.
Project Structure
We’ll add new files and folders to the existing project structure:
beauty-salon-app/
│
├── .env
├── package.json
├── server.js
├── config/
│ └── db.js
├── controllers/
│ └── auth.js
│ └── customer.js
│ └── owner.js
│ └── admin.js
│ └── notification.js
│ └── billing.js
├── middleware/
│ └── auth.js
├── models/
│ └── User.js
│ └── Service.js
│ └── Booking.js
│ └── Salon.js
│ └── Billing.js
├── routes/
│ └── auth.js
│ └── customer.js
│ └── owner.js
│ └── admin.js
│ └── notification.js
│ └── billing.js
└── utils/
└── payment.js
└── sms.js
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
Step 3: Define the Billing Model
Create a schema for billing records.
File: models/Billing.js
const mongoose = require('mongoose');
// Define the billing schema
const billingSchema = new mongoose.Schema({
customer: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
amount: { type: Number, required: true },
paymentIntentId: { type: String, required: true },
status: { type: String, required: true },
createdAt: { type: Date, default: Date.now },
});
const Billing = mongoose.model('Billing', billingSchema);
module.exports = Billing;
- billingSchema: Defines the structure of billing documents.
- customer, amount, paymentIntentId, status, createdAt: Fields in the billing schema.
- customer: References the user who made the payment.
Step 4: Implement the Billing Controller
Create a file to handle the logic for billing-related operations.
File: controllers/billing.js
const Billing = require('../models/Billing');
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 = new Billing({
customer: customerId,
amount: paymentIntent.amount,
paymentIntentId,
status: paymentIntent.status,
});
await billing.save();
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.find({ customer: req.user.id });
res.json(billingHistory);
} catch (error) {
res.status(500).json({ error: error.message });
}
};
- 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 5: 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,
currency: 'usd',
});
return paymentIntent;
} catch (error) {
throw new Error(error.message);
}
};
- createPaymentIntent: Creates a payment intent using Stripe.
- amount: The amount to be charged.
Step 6: 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;
- 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 7: 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');
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, () => {
console.log(`Server is running on port ${PORT}`);
});
connectDB();
- 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.
Project Structure
We’ll add new files and folders to the existing project structure:
beauty-salon-app/
│
├── .env
├── package.json
├── server.js
├── config/
│ └── db.js
├── controllers/
│ └── auth.js
│ └── customer.js
│ └── owner.js
│ └── admin.js
│ └── notification.js
│ └── billing.js
│ └── photo.js
├── middleware/
│ └── auth.js
├── models/
│ └── User.js
│ └── Service.js
│ └── Booking.js
│ └── Salon.js
│ └── Billing.js
│ └── Photo.js
├── routes/
│ └── auth.js
│ └── customer.js
│ └── owner.js
│ └── admin.js
│ └── notification.js
│ └── billing.js
│ └── photo.js
└── utils/
└── payment.js
└── sms.js
└── cloudinary.js
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
Step 3: Define the Photo Model
Create a schema for photos.
File: models/Photo.js
const mongoose = require('mongoose');
// Define the photo schema
const photoSchema = new mongoose.Schema({
url: { type: String, required: true },
description: { type: String },
uploadedBy: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
service: { type: mongoose.Schema.Types.ObjectId, ref: 'Service', required: true },
createdAt: { type: Date, default: Date.now },
});
const Photo = mongoose.model('Photo', photoSchema);
module.exports = Photo;
- photoSchema: Defines the structure of photo documents.
- url, description, uploadedBy, service, createdAt: Fields in the photo schema.
- uploadedBy: References the user who uploaded the photo.
- service: 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;
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;
- 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 = new Photo({
url: path,
description,
uploadedBy: req.user.id,
service: serviceId,
});
await photo.save();
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.find({ service: serviceId }).populate('uploadedBy', 'username');
res.json(photos);
} catch (error) {
res.status(500).json({ error: error.message });
}
};
- 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.
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;
- 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');
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, () => {
console.log(`Server is running on port ${PORT}`);
});
connectDB();
- 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.
Project Structure
We’ll add new files and folders to the existing project structure:
beauty-salon-app/
│
├── .env
├── package.json
├── server.js
├── config/
│ └── db.js
├── controllers/
│ └── auth.js
│ └── customer.js
│ └── owner.js
│ └── admin.js
│ └── notification.js
│ └── billing.js
│ └── photo.js
│ └── revenue.js
├── middleware/
│ └── auth.js
├── models/
│ └── User.js
│ └── Service.js
│ └── Booking.js
│ └── Salon.js
│ └── Billing.js
│ └── Revenue.js
├── routes/
│ └── auth.js
│ └── customer.js
│ └── owner.js
│ └── admin.js
│ └── notification.js
│ └── billing.js
│ └── photo.js
│ └── revenue.js
└── utils/
└── payment.js
└── sms.js
└── cloudinary.js
Step 1: Define the Revenue Model
Create a schema for revenue-related data.
File: models/Revenue.js
const mongoose = require('mongoose');
// Define the revenue schema
const revenueSchema = new mongoose.Schema({
salon: { type: mongoose.Schema.Types.ObjectId, ref: 'Salon', required: true },
type: { type: String, enum: ['Listing', 'Commission', 'Subscription', 'Advertisement'], required: true },
amount: { type: Number, required: true },
createdAt: { type: Date, default: Date.now },
});
const Revenue = mongoose.model('Revenue', revenueSchema);
module.exports = Revenue;
- revenueSchema: Defines the structure of revenue documents.
- salon, type, amount, createdAt: Fields in the revenue schema.
- salon: References the salon associated with the revenue.
Step 2: 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 = new Revenue({
salon: salonId,
type,
amount,
});
await revenue.save();
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.find({ salon: 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.find().populate('salon', 'name');
res.json(revenue);
} catch (error) {
res.status(500).json({ error: error.message });
}
};
- 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 3: 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;
- 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 4: 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');
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, () => {
console.log(`Server is running on port ${PORT}`);
});
connectDB();
- app.use(‘/revenue’, revenueRouter): Mounts the revenue routes at
/revenue
.
Step 5: 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 Revenue = require('../models/Revenue');
// Controller to get all revenue records
exports.getAllRevenue = async (req, res) => {
try {
const revenue = await Revenue.find().populate('salon', 'name');
res.json(revenue);
} catch (error) {
res.status(500).json({ error: error.message });
}
};
- 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.
Project Structure
We’ll add new files and folders to the existing project structure:
beauty-salon-app/
│
├── .env
├── package.json
├── server.js
├── config/
│ └── db.js
├── controllers/
│ └── auth.js
│ └── customer.js
│ └── owner.js
│ └── admin.js
│ └── notification.js
│ └── billing.js
│ └── photo.js
│ └── revenue.js
├── middleware/
│ └── auth.js
├── models/
│ └── User.js
│ └── Service.js
│ └── Booking.js
│ └── Salon.js
│ └── Billing.js
│ └── Photo.js
│ └── Revenue.js
├── routes/
│ └── auth.js
│ └── customer.js
│ └── owner.js
│ └── admin.js
│ └── notification.js
│ └── billing.js
│ └── photo.js
│ └── revenue.js
├── tests/
│ └── auth.test.js
│ └── customer.test.js
│ └── owner.test.js
│ └── admin.test.js
│ └── notification.test.js
│ └── billing.test.js
│ └── photo.test.js
│ └── revenue.test.js
└── utils/
└── payment.js
└── sms.js
└── cloudinary.js
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'); // Assuming server.js exports the app
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');
});
});
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: '60d21b4667d0d8992e610c85', // Replace with a valid service ID
bookingDate: '2023-06-01',
});
expect(res.statusCode).toEqual(201);
expect(res.body).toHaveProperty('message');
});
});
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
heroku login
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
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');
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, () => {
console.log(`Server is running on port ${PORT}`);
});
connectDB();
module.exports = app; // Export the app for testing
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.
Project Structure
We’ll add new files and folders to the existing project structure:
beauty-salon-app/
│
├── .env
├── package.json
├── server.js
├── config/
│ └── db.js
├── controllers/
│ └── auth.js
│ └── customer.js
│ └── owner.js
│ └── admin.js
│ └── notification.js
│ └── billing.js
│ └── photo.js
│ └── revenue.js
│ └── analytics.js
├── middleware/
│ └── auth.js
├── models/
│ └── User.js
│ └── Service.js
│ └── Booking.js
│ └── Salon.js
│ └── Billing.js
│ └── Photo.js
│ └── Revenue.js
├── routes/
│ └── auth.js
│ └── customer.js
│ └── owner.js
│ └── admin.js
│ └── notification.js
│ └── billing.js
│ └── photo.js
│ └── revenue.js
│ └── analytics.js
└── utils/
└── payment.js
└── sms.js
└── cloudinary.js
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 = require('../models/User');
const Booking = require('../models/Booking');
const Revenue = require('../models/Revenue');
const Salon = require('../models/Salon');
// Controller to get user activity report
exports.getUserActivityReport = async (req, res) => {
try {
const userCount = await User.countDocuments();
const bookingCount = await Booking.countDocuments();
const revenueCount = await Revenue.countDocuments();
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.aggregate([
{
$group: {
_id: '$type',
totalAmount: { $sum: '$amount' },
},
},
]);
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.aggregate([
{
$group: {
_id: { year: { $year: '$bookingDate' }, month: { $month: '$bookingDate' } },
totalBookings: { $sum: 1 },
},
},
{
$sort: { '_id.year': 1, '_id.month': 1 },
},
]);
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.countDocuments();
const activeSalons = await Salon.find({ status: 'Active' }).countDocuments();
res.json({
salonCount,
activeSalons,
});
} catch (error) {
res.status(500).json({ error: error.message });
}
};
- 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 and year.
- getSalonReport: Retrieves a report on the number of salons and active salons.
Step 2: 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;
- 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 3: 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');
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, () => {
console.log(`Server is running on port ${PORT}`);
});
connectDB();
- app.use(‘/analytics’, analyticsRouter): Mounts the analytics routes at
/analytics
.
Step 4: 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');
});
});
- 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.
With the core modules implemented, there are a few additional considerations and final touches to ensure the beauty salon booking app is comprehensive, scalable, and maintainable.
Additional Considerations
1. Error Handling and Logging
Implement robust error handling and logging mechanisms to ensure issues can be tracked and resolved efficiently.
Global Error Handler
Create a middleware for global error handling.
// File: middleware/errorHandler.js
const errorHandler = (err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ error: err.message });
};
module.exports = errorHandler;
Update the server.js
to use the error handler.
// File: server.js
const errorHandler = require('./middleware/errorHandler');
// other code...
app.use(errorHandler); // Add this line at the end
Logging
Install and configure a logging library like Winston.
npm install winston
What is Logging? Logging is the process of recording information about the application’s runtime behavior. Logs capture events, errors, and other significant occurrences, providing insights into the application’s operations.
Use of Logging:
- Debugging: Helps developers understand what the application is doing and diagnose issues.
- Monitoring: Enables tracking of application health and performance.
- Audit Trails: Keeps a record of user actions and system changes for compliance and analysis.
- Error Tracking: Captures and stores error details for future review and resolution
Create a logger configuration.
// File: config/logger.js
const { createLogger, format, transports } = require('winston');
const logger = createLogger({
level: 'info',
format: format.combine(
format.colorize(),
format.timestamp(),
format.printf(({ timestamp, level, message }) => `${timestamp} [${level}]: ${message}`)
),
transports: [
new transports.Console(),
new transports.File({ filename: 'logs/error.log', level: 'error' }),
new transports.File({ filename: 'logs/combined.log' }),
],
});
module.exports = logger;
Use the logger in your application.
// File: server.js
const logger = require('./config/logger');
// other code...
app.listen(PORT, () => {
logger.info(`Server is running on port ${PORT}`);
});
2. Rate Limiting and Security
Implement rate limiting and other security best practices to protect the application from abuse.
What is Rate Limiting? Rate limiting controls the number of requests a client can make to the server within a specified time frame. This prevents abuse and ensures fair usage of resources.
Use of Rate Limiting:
- Protection Against DoS Attacks: Prevents denial-of-service attacks by limiting the number of requests.
- Resource Management: Ensures server resources are not overwhelmed by too many requests.
- Fair Usage: Ensures all users get fair access to the application.
What is Security in Web Applications? Security involves implementing measures to protect applications from threats and vulnerabilities.
Security Practices:
- Setting Security Headers: Using libraries like
helmet
to set HTTP headers for security. - Data Validation and Sanitization: Ensuring data from users is validated and sanitized to prevent SQL injection and XSS attacks.
- Authentication and Authorization: Implementing robust authentication and authorization mechanisms.
npm install express-rate-limit helmet
Configure rate limiting and security headers.
// File: server.js
const rateLimit = require('express-rate-limit');
const helmet = require('helmet');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
});
// other code...
app.use(helmet());
app.use(limiter);
3. Documentation
Ensure the API is well-documented for developers and users. Use tools like Swagger for API documentation.
What is Swagger? Swagger is an open-source toolset for documenting APIs. It helps developers design, build, and document RESTful APIs. The Swagger UI provides a web-based interface for exploring and testing APIs.
Use of Swagger:
- API Documentation: Provides interactive API documentation that developers can use to understand and test API endpoints.
- Development Aid: Helps in designing and developing APIs with a clear contract.
- Client Generation: Automatically generates client libraries in various languages based on the API documentation.
npm install swagger-ui-express swagger-jsdoc
Configure Swagger.
// File: config/swagger.js
const swaggerJsdoc = require('swagger-jsdoc');
const swaggerUi = require('swagger-ui-express');
const options = {
swaggerDefinition: {
openapi: '3.0.0',
info: {
title: 'Beauty Salon Booking API',
version: '1.0.0',
description: 'API documentation for the Beauty Salon Booking App',
},
servers: [
{
url: 'http://localhost:3000',
},
],
},
apis: ['./routes/*.js'], // Path to the API docs
};
const specs = swaggerJsdoc(options);
module.exports = (app) => {
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(specs));
};
Integrate Swagger in server.js
.
// File: server.js
const swaggerSetup = require('./config/swagger');
// other code...
swaggerSetup(app); // Add this line
4. Performance Optimization
Consider performance optimization techniques like query optimization, caching, and efficient data handling.
Caching
Use a caching solution like Redis to cache frequently accessed data.
npm install redis
What is Redis? Redis is an open-source, in-memory data structure store used as a database, cache, and message broker. It supports various data structures such as strings, hashes, lists, sets, and more.
Use of Redis:
- Caching: Stores frequently accessed data in memory to improve application performance.
- Session Management: Manages user sessions in a scalable way.
- Message Queues: Used for building robust messaging and task queues.
Configure Redis.
// File: config/redis.js
const redis = require('redis');
const client = redis.createClient();
client.on('connect', () => {
console.log('Connected to Redis');
});
client.on('error', (err) => {
console.error('Redis error:', err);
});
module.exports = client;
Use Redis in your controllers.
// File: controllers/customer.js
const redisClient = require('../config/redis');
exports.getAllServices = async (req, res) => {
redisClient.get('services', async (err, services) => {
if (err) throw err;
if (services) {
res.json(JSON.parse(services));
} else {
const services = await Service.find().populate('createdBy', 'username');
redisClient.setex('services', 3600, JSON.stringify(services));
res.json(services);
}
});
};
5. Scalability and Load Balancing
Ensure the application can handle increased load by using load balancers and scalable infrastructure.
Final Steps
- Code Review: Conduct thorough code reviews to ensure code quality and maintainability.
- Continuous Integration/Continuous Deployment (CI/CD): Ensure the CI/CD pipeline is properly set up and functioning.
- User Feedback: Collect feedback from users and stakeholders to make iterative improvements.
- Monitoring and Analytics: Set up monitoring tools to keep track of application performance and health.
The beauty salon booking app now has a comprehensive set of features, from user authentication to analytics and reporting. By following best practices in error handling, security, documentation, performance optimization, and scalability, the application is robust and ready for production. The step-by-step approach ensures that beginners can understand each part of the code and its purpose, leading to a maintainable and scalable 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.