Welcome to this comprehensive guide on building a car repair backend shop application using Node.js, Express, and MongoDB. This course is designed to take you step-by-step through the process of creating a robust, scalable, and secure backend system for managing a car repair shop. Whether you are an aspiring developer or a seasoned professional looking to enhance your skills, this guide will provide you with the knowledge and tools necessary to develop a full-featured backend application.
Course Overview
In the automotive service industry, efficient management of repair shops, employees, vehicles, and customer interactions is crucial for business success. This course will help you build an application that streamlines these processes, ensuring smooth operation and excellent customer service. By the end of this course, you will have a fully functional backend system capable of handling everything from user authentication to billing and notifications.
Key Features of the Application
User Authentication and Role-Based Access Control (RBAC): Secure your application with robust user authentication mechanisms. Implement role-based access control to ensure that only authorized personnel can access specific functionalities, such as adding cars or updating repair statuses.
Employee Management: Keep track of your employees, their roles, and their activities. Manage employee status and maintain a detailed record of their schedules and tasks performed.
Car Management: Efficiently manage car information, including VIN, license plates, customer details, and repair statuses. Allow employees to update the repair status and log actions performed on each vehicle.
Pagination, Filtering, and Search: Implement advanced data retrieval techniques to ensure efficient access to large datasets. Enhance user experience by enabling easy search, filtering, and pagination of records.
Notification System: Keep customers informed with real-time notifications. Send email and SMS updates about the status of their vehicle repairs, ensuring they are always in the loop.
Billing System: Integrate a robust billing system using Stripe for seamless payment processing. Generate invoices, apply discounts, and manage payment statuses securely.
File Upload and Storage: Enable employees to upload photos of car parts and repairs, storing them securely using Multer and Cloudinary. Enhance transparency and communication with customers by providing visual updates on repair progress.
Admin Area: Develop a comprehensive admin interface to manage all aspects of the application. View detailed records of customers, employees, and repairs, and gain insights into overall shop performance.
Visits and Offers Management: Track customer visits and manage service offers efficiently. Record planned and actual visit times, associated services, and offers, ensuring accurate and up-to-date information.
Course Structure
This course is structured into modules, each focusing on a specific aspect of the application. Starting with the initial setup and installation of dependencies, you will gradually build up the functionality, layer by layer. Each module includes detailed explanations and instructions to help you understand the underlying concepts and implement them effectively.
Learning Outcomes
By completing this course, you will:
- Gain proficiency in using Node.js, Express, and MongoDB for backend development.
- Learn how to implement secure user authentication and authorization mechanisms.
- Understand how to manage complex data relationships and ensure data integrity.
- Develop skills in building scalable and efficient backend systems.
- Gain experience with third-party integrations, such as Stripe and Cloudinary.
- Enhance your problem-solving skills and ability to build real-world applications.
Prerequisites
To get the most out of this course, you should have a basic understanding of JavaScript and familiarity with Node.js. Knowledge of databases, particularly MongoDB, will be beneficial but is not mandatory. Whether you are a beginner or have some experience in backend development, this course is designed to accommodate various skill levels.
Module 1: Initial Setup and Installation
In this module, we will set up the foundational elements of our car repair backend shop application. This involves installing the necessary tools and dependencies, creating the project directory, initializing the project with npm, and setting up MongoDB. By the end of this module, you will have a solid base to build upon as we progress through the course.
Objectives
- Install Node.js and npm.
- Create and initialize the project directory.
- Install essential dependencies.
- Set up MongoDB.
- Establish the initial directory structure and configuration files.
Step-by-Step Guide
1. Install Node.js and npm
Node.js is a JavaScript runtime built on Chrome’s V8 JavaScript engine, and npm is the package manager for Node.js. They are essential for developing server-side applications.
- Download and Install: Visit the Node.js official website and download the latest stable version. The installation includes npm.
- Verify Installation:
node -v
npm -v
2. Create and Initialize the Project Directory
Create a directory for your project and initialize it with npm. This will create a package.json
file that will manage the project’s dependencies and scripts.
- Create Project Directory
mkdir car-repair-shop
cd car-repair-shop
Initialize npm
npm init -y
3. Install Essential Dependencies
We need several packages to build our application. These include Express for building our server, Mongoose for interacting with MongoDB, bcrypt for hashing passwords, JSON Web Tokens (JWT) for authentication, and others.
- Install Dependencies
npm install express mongoose bcryptjs jsonwebtoken cookie-parser multer nodemailer dotenv cloudinary stripe
4. Set Up MongoDB
MongoDB is a NoSQL database that we will use to store our application data. You can choose to install MongoDB locally or use a cloud-based solution like MongoDB Atlas.
Install MongoDB Locally: Follow the instructions on the MongoDB installation guide.
Set Up MongoDB Atlas:
- Go to the MongoDB Atlas website.
- Sign up and create a new cluster.
- Get your connection string to use in your application.
5. Establish Initial Directory Structure and Configuration Files
Create the initial directory structure to organize your code effectively.
Directory Structure:
car-repair-shop/
├── models/
├── routes/
├── controllers/
├── middlewares/
├── config/
├── utils/
├── .env
├── app.js
└── package.json
Create Environment Configuration File: In the root directory, create a .env
file to store environment variables. This file will include sensitive information such as database connection strings, JWT secrets, and API keys.
MONGO_URI=your_mongodb_connection_string
JWT_SECRET=your_jwt_secret
CLOUDINARY_URL=your_cloudinary_url
STRIPE_SECRET_KEY=your_stripe_secret_key
EMAIL_SERVICE=your_email_service
EMAIL_USER=your_email_user
EMAIL_PASS=your_email_pass
By completing this module, you have successfully set up the foundational elements of your car repair backend shop application. You have installed Node.js and npm, created and initialized your project directory, installed essential dependencies, set up MongoDB, and established the initial directory structure. This foundation is crucial for the subsequent modules, where we will build upon this setup to add more functionality and features to the application.
Module 2: Database Configuration
In this module, we will configure MongoDB for our application. This involves setting up a connection to the MongoDB database and creating a configuration file to manage this connection. By the end of this module, you will have a working connection to MongoDB that can be used throughout the application.
Objectives
- Create a configuration file for MongoDB.
- Set up environment variables for secure configuration management.
- Initialize the MongoDB connection in the main application file.
Step-by-Step Guide
1. Create a Configuration File for MongoDB
We will create a configuration file to handle the MongoDB connection. This file will establish a connection to the database and export this connection for use in other parts of the application.
Create Configuration Directory: In the root directory, create a
config
directory to store configuration files.
mkdir config
Create Database Configuration File: Inside the config
directory, create a file named db.js
.
- File:
config/db.js
const mongoose = require('mongoose');
const dotenv = require('dotenv');
dotenv.config();
const connectDB = async () => {
try {
await mongoose.connect(process.env.MONGO_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
useCreateIndex: true,
});
console.log('MongoDB Connected');
} catch (error) {
console.error(`Error: ${error.message}`);
process.exit(1);
}
};
module.exports = connectDB;
2. Set Up Environment Variables
Environment variables are essential for storing sensitive information securely. We will use the .env
file created in the previous module to store the MongoDB connection string and other configurations.
- Update
.env
File: Add your MongoDB connection string to the.env
file.- File:
.env
- File:
MONGO_URI=your_mongodb_connection_string
JWT_SECRET=your_jwt_secret
CLOUDINARY_URL=your_cloudinary_url
STRIPE_SECRET_KEY=your_stripe_secret_key
EMAIL_SERVICE=your_email_service
EMAIL_USER=your_email_user
EMAIL_PASS=your_email_pass
3. Initialize MongoDB Connection in Main Application File
Next, we will modify the main application file (app.js
) to initialize the MongoDB connection using the configuration file we created.
- Update
app.js
:- File:
app.js
- File:
const express = require('express');
const connectDB = require('./config/db');
const dotenv = require('dotenv');
dotenv.config();
connectDB();
const app = express();
app.use(express.json());
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
By completing this module, you have successfully configured MongoDB for your application. You created a configuration file to handle the database connection, set up environment variables for secure configuration management, and initialized the MongoDB connection in the main application file. This setup ensures that your application can connect to the database securely and efficiently, providing a solid foundation for data management in subsequent modules.
Module 3: User Authentication
In this module, we will implement user authentication to allow secure access to the application. This includes creating user models, handling user registration and login, and implementing JWT-based authentication. This is crucial for ensuring that only authorized users can access and perform certain actions within the application.
Objectives
- Create a User model for MongoDB.
- Develop user registration and login controllers.
- Set up routes for user authentication.
- Implement middleware for parsing JSON requests and managing authentication.
Step-by-Step Guide
1. Create User Model
The User model will represent the structure of user data in the MongoDB database. This model includes fields for user details and methods for password hashing and comparison.
Create Models Directory: Ensure the
models
directory exists in the root of your project.Create User Model: Inside the
models
directory, create a file namedUser.js
.- File:
models/User.js
- File:
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
const UserSchema = new mongoose.Schema({
firstName: { type: String, required: true },
lastName: { type: String, required: true },
email: { type: String, required: true, unique: true },
password: { type: String, required: true },
role: { type: String, enum: ['admin', 'owner', 'employee'], required: true },
isActive: { type: Boolean, default: true },
employmentStartDate: { type: Date, default: Date.now },
employmentEndDate: { type: Date },
});
UserSchema.pre('save', async function (next) {
if (!this.isModified('password')) {
next();
}
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.password, salt);
next();
});
UserSchema.methods.matchPassword = async function (enteredPassword) {
return await bcrypt.compare(enteredPassword, this.password);
};
module.exports = mongoose.model('User', UserSchema);
2. Develop User Registration and Login Controllers
Controllers handle the business logic for user registration and login. They interact with the User model to create new users and verify existing users’ credentials.
Create Controllers Directory: Ensure the
controllers
directory exists in the root of your project.Create Auth Controller: Inside the
controllers
directory, create a file namedauthController.js
.- File:
controllers/authController.js
- File:
const User = require('../models/User');
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
const generateToken = (id) => {
return jwt.sign({ id }, process.env.JWT_SECRET, {
expiresIn: '30d',
});
};
exports.registerUser = async (req, res) => {
const { firstName, lastName, email, password, role } = req.body;
try {
const user = await User.create({
firstName,
lastName,
email,
password,
role,
});
res.status(201).json({
_id: user._id,
firstName: user.firstName,
lastName: user.lastName,
email: user.email,
role: user.role,
token: generateToken(user._id),
});
} catch (error) {
res.status(400).json({ message: error.message });
}
};
exports.loginUser = async (req, res) => {
const { email, password } = req.body;
try {
const user = await User.findOne({ email });
if (user && (await user.matchPassword(password))) {
res.json({
_id: user._id,
firstName: user.firstName,
lastName: user.lastName,
email: user.email,
role: user.role,
token: generateToken(user._id),
});
} else {
res.status(401).json({ message: 'Invalid email or password' });
}
} catch (error) {
res.status(500).json({ message: error.message });
}
};
3. Set Up Routes for User Authentication
Routes define the endpoints for user registration and login, linking them to the corresponding controller methods.
Create Routes Directory: Ensure the
routes
directory exists in the root of your project.Create Auth Routes: Inside the
routes
directory, create a file namedauthRoutes.js
.- File:
routes/authRoutes.js
- File:
const express = require('express');
const { registerUser, loginUser } = require('../controllers/authController');
const router = express.Router();
router.post('/register', registerUser);
router.post('/login', loginUser);
module.exports = router;
4. Implement Middleware for Parsing JSON Requests and Managing Authentication
Middleware functions process requests before they reach the route handlers. We will add middleware to parse JSON requests and manage authentication.
- Update
app.js
:- Import the authentication routes and add middleware to handle JSON requests.
- File:
app.js
const express = require('express');
const connectDB = require('./config/db');
const dotenv = require('dotenv');
const authRoutes = require('./routes/authRoutes');
dotenv.config();
connectDB();
const app = express();
app.use(express.json());
app.use('/api/auth', authRoutes);
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
By completing this module, you have implemented user authentication for your car repair backend shop application. You created a User model to store user details, developed controllers for user registration and login, set up routes for authentication endpoints, and implemented middleware to handle JSON requests and manage authentication. This foundational functionality ensures that your application can securely manage user access and roles.
Module 4: Role-Based Access Control (RBAC) and Middleware
In this module, we will implement Role-Based Access Control (RBAC) to manage user permissions and ensure that only authorized users can perform specific actions. We will also create middleware functions to protect routes and verify JSON Web Tokens (JWTs). This module is essential for securing the application and enforcing user roles.
Objectives
- Implement RBAC to restrict access based on user roles.
- Create middleware to protect routes and verify JWT tokens.
- Apply middleware to appropriate routes to ensure secure access control.
Step-by-Step Guide
1. Implement Role-Based Access Control (RBAC)
RBAC involves defining roles and permissions for users. In this application, we have three roles: admin, owner, and employee. Each role will have specific permissions to perform certain actions.
2. Create Middleware for Authorization
We will create middleware functions to protect routes and verify JWT tokens. These middleware functions will check if the user is authenticated and if they have the necessary permissions to access the route.
Create Middleware Directory: Ensure the
middlewares
directory exists in the root of your project.Create Auth Middleware: Inside the
middlewares
directory, create a file namedauthMiddleware.js
.- File:
middlewares/authMiddleware.js
- File:
const jwt = require('jsonwebtoken');
const User = require('../models/User');
const protect = async (req, res, next) => {
let token;
if (
req.headers.authorization &&
req.headers.authorization.startsWith('Bearer')
) {
try {
token = req.headers.authorization.split(' ')[1];
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = await User.findById(decoded.id).select('-password');
next();
} catch (error) {
res.status(401).json({ message: 'Not authorized, token failed' });
}
} else {
res.status(401).json({ message: 'Not authorized, no token' });
}
};
const admin = (req, res, next) => {
if (req.user && req.user.role === 'admin') {
next();
} else {
res.status(401).json({ message: 'Not authorized as an admin' });
}
};
const owner = (req, res, next) => {
if (req.user && req.user.role === 'owner') {
next();
} else {
res.status(401).json({ message: 'Not authorized as an owner' });
}
};
module.exports = { protect, admin, owner };
3. Apply Middleware to Routes
We will apply the middleware functions to the routes that require protection. This ensures that only authenticated users with the correct roles can access these routes.
Update Auth Routes:
- File:
routes/authRoutes.js
- File:
const express = require('express');
const { registerUser, loginUser } = require('../controllers/authController');
const { protect, admin, owner } = require('../middlewares/authMiddleware');
const router = express.Router();
router.post('/register', protect, admin, registerUser);
router.post('/login', loginUser);
module.exports = router;
Example of Protecting Routes: Ensure that routes for adding cars, updating car details, and other sensitive operations are protected using the middleware.
- Example Route File:
routes/carRoutes.js
const express = require('express');
const { addCar, updateCar } = require('../controllers/carController');
const { protect, admin } = require('../middlewares/authMiddleware');
const router = express.Router();
router.post('/add', protect, admin, addCar);
router.put('/update/:id', protect, admin, updateCar);
module.exports = router;
By completing this module, you have implemented Role-Based Access Control (RBAC) and middleware for your car repair backend shop application. You created middleware functions to protect routes and verify JWT tokens, ensuring that only authenticated users with the correct roles can access certain routes. This setup is crucial for maintaining the security and integrity of your application, ensuring that users can only perform actions they are authorized to do.
Module 5: Employee Management
In this module, we will focus on managing employee data and statuses. This includes creating the necessary models, controllers, and routes to handle employee management functionalities such as listing employees, updating their statuses, and managing their schedules. This module ensures that the admin can efficiently manage employee details and their activity within the repair shop.
Objectives
- Create models to represent employee data.
- Develop controllers to handle employee-related operations.
- Set up routes for managing employees.
- Ensure that only authorized users (e.g., admin) can manage employee data.
Step-by-Step Guide
1. Create Employee Models
We will extend the existing User model to include additional fields related to employee management, such as employment dates and positions.
- Update User Model:
- File:
models/User.js
- File:
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
const UserSchema = new mongoose.Schema({
firstName: { type: String, required: true },
lastName: { type: String, required: true },
email: { type: String, required: true, unique: true },
password: { type: String, required: true },
role: { type: String, enum: ['admin', 'owner', 'employee'], required: true },
isActive: { type: Boolean, default: true },
employmentStartDate: { type: Date, default: Date.now },
employmentEndDate: { type: Date },
positionId: { type: mongoose.Schema.Types.ObjectId, ref: 'Position' },
cityId: { type: mongoose.Schema.Types.ObjectId, ref: 'City' },
});
UserSchema.pre('save', async function (next) {
if (!this.isModified('password')) {
next();
}
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.password, salt);
next();
});
UserSchema.methods.matchPassword = async function (enteredPassword) {
return await bcrypt.compare(enteredPassword, this.password);
};
module.exports = mongoose.model('User', UserSchema);
2. Develop Employee Controllers
Controllers handle the business logic for managing employees. We will create controllers to fetch employee details and update their statuses.
- Create Employee Controller: Inside the
controllers
directory, create a file namedemployeeController.js
.- File:
controllers/employeeController.js
- File:
const User = require('../models/User');
exports.getEmployees = async (req, res) => {
try {
const employees = await User.find({ role: 'employee' }).populate('positionId cityId');
res.json(employees);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
exports.updateEmployeeStatus = async (req, res) => {
const { employeeId, isActive } = req.body;
try {
const employee = await User.findById(employeeId);
if (employee) {
employee.isActive = isActive;
await employee.save();
res.json({ message: 'Employee status updated' });
} else {
res.status(404).json({ message: 'Employee not found' });
}
} catch (error) {
res.status(500).json({ message: error.message });
}
};
3. Set Up Routes for Managing Employees
Routes define the endpoints for managing employee-related operations, linking them to the corresponding controller methods.
- Create Employee Routes: Inside the
routes
directory, create a file namedemployeeRoutes.js
.- File:
routes/employeeRoutes.js
- File:
const express = require('express');
const { getEmployees, updateEmployeeStatus } = require('../controllers/employeeController');
const { protect, admin } = require('../middlewares/authMiddleware');
const router = express.Router();
router.get('/', protect, admin, getEmployees);
router.put('/status', protect, admin, updateEmployeeStatus);
module.exports = router;
4. Apply Middleware to Protect Routes
Ensure that the routes for managing employees are protected and can only be accessed by authorized users (e.g., admin).
- Update
app.js
: Import the employee routes and add middleware to handle JSON requests.- File:
app.js
- File:
const express = require('express');
const connectDB = require('./config/db');
const dotenv = require('dotenv');
const authRoutes = require('./routes/authRoutes');
const employeeRoutes = require('./routes/employeeRoutes');
dotenv.config();
connectDB();
const app = express();
app.use(express.json());
app.use('/api/auth', authRoutes);
app.use('/api/employees', employeeRoutes);
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
By completing this module, you have implemented employee management functionality for your car repair backend shop application. You created models to represent employee data, developed controllers to handle employee-related operations, set up routes for managing employees, and ensured that only authorized users can access these routes. This setup allows the admin to efficiently manage employee details and their activities within the repair shop, contributing to better organizational management and workflow.
Module 6: Car Management
In this module, we will focus on managing car information and repair statuses. This includes creating models to represent car data, developing controllers to handle car-related operations, and setting up routes for adding and updating car details. This module ensures that the admin can efficiently manage car details, and employees can update repair statuses.
Objectives
- Create models to represent car data.
- Develop controllers to handle car-related operations.
- Set up routes for managing cars.
- Ensure that only authorized users (e.g., admin) can add new cars, and employees can update repair statuses.
Step-by-Step Guide
1. Create Car Models
We will create models to represent car data, including vehicle information, customer references, and repair status.
- Create Car Model: Inside the
models
directory, create a file namedCar.js
.- File:
models/Car.js
- File:
const mongoose = require('mongoose');
const CarSchema = new mongoose.Schema({
vin: { type: String, required: true, unique: true },
licensePlate: { type: String, required: true },
customer: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
model: { type: String, required: true },
manufacturedYear: { type: Number, required: true },
manufacturedMonth: { type: Number, required: true },
details: { type: String },
repairStatus: {
type: String,
enum: ['pending', 'in-progress', 'completed'],
default: 'pending',
},
repairLogs: [
{
employee: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
status: { type: String },
description: { type: String },
date: { type: Date, default: Date.now },
},
],
insertTs: { type: Date, default: Date.now },
});
module.exports = mongoose.model('Car', CarSchema);
2. Develop Car Controllers
Controllers handle the business logic for managing cars. We will create controllers to add new cars, update car information, and track repair statuses.
- Create Car Controller: Inside the
controllers
directory, create a file namedcarController.js
.- File:
controllers/carController.js
- File:
const Car = require('../models/Car');
const User = require('../models/User');
exports.addCar = async (req, res) => {
const { vin, licensePlate, customer, model, manufacturedYear, manufacturedMonth, details } = req.body;
try {
const car = new Car({
vin,
licensePlate,
customer,
model,
manufacturedYear,
manufacturedMonth,
details,
});
await car.save();
res.status(201).json(car);
} catch (error) {
res.status(400).json({ message: error.message });
}
};
exports.updateCar = async (req, res) => {
const { id } = req.params;
const { licensePlate, model, details } = req.body;
try {
const car = await Car.findById(id);
if (car) {
car.licensePlate = licensePlate || car.licensePlate;
car.model = model || car.model;
car.details = details || car.details;
await car.save();
res.json(car);
} else {
res.status(404).json({ message: 'Car not found' });
}
} catch (error) {
res.status(400).json({ message: error.message });
}
};
exports.updateRepairStatus = async (req, res) => {
const { id } = req.params;
const { status, description } = req.body;
try {
const car = await Car.findById(id);
if (car) {
car.repairStatus = status;
car.repairLogs.push({ employee: req.user._id, status, description });
await car.save();
res.json(car);
} else {
res.status(404).json({ message: 'Car not found' });
}
} catch (error) {
res.status(400).json({ message: error.message });
}
};
exports.getCars = async (req, res) => {
try {
const cars = await Car.find().populate('customer repairLogs.employee');
res.json(cars);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
3. Set Up Routes for Managing Cars
Routes define the endpoints for managing car-related operations, linking them to the corresponding controller methods.
- Create Car Routes: Inside the
routes
directory, create a file namedcarRoutes.js
.- File:
routes/carRoutes.js
- File:
const express = require('express');
const {
addCar,
updateCar,
updateRepairStatus,
getCars,
} = require('../controllers/carController');
const { protect, admin, owner, employee } = require('../middlewares/authMiddleware');
const router = express.Router();
router.post('/add', protect, admin, addCar);
router.put('/update/:id', protect, admin, updateCar);
router.put('/status/:id', protect, employee, updateRepairStatus);
router.get('/', protect, getCars);
module.exports = router;
4. Apply Middleware to Protect Routes
Ensure that the routes for managing cars are protected and can only be accessed by authorized users (e.g., admin for adding new cars, employees for updating repair statuses).
- Update
app.js
: Import the car routes and add middleware to handle JSON requests.- File:
app.js
- File:
const express = require('express');
const connectDB = require('./config/db');
const dotenv = require('dotenv');
const authRoutes = require('./routes/authRoutes');
const employeeRoutes = require('./routes/employeeRoutes');
const carRoutes = require('./routes/carRoutes');
dotenv.config();
connectDB();
const app = express();
app.use(express.json());
app.use('/api/auth', authRoutes);
app.use('/api/employees', employeeRoutes);
app.use('/api/cars', carRoutes);
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
By completing this module, you have implemented car management functionality for your car repair backend shop application. You created models to represent car data, developed controllers to handle car-related operations, set up routes for managing cars, and ensured that only authorized users can access these routes. This setup allows the admin to efficiently manage car details, and employees can update repair statuses, contributing to better organizational management and workflow
Module 7: Pagination, Filtering, and Search
In this module, we will implement pagination, filtering, and search functionalities to efficiently manage and retrieve large datasets. These features enhance the user experience by allowing users to easily navigate through records, apply filters, and perform searches.
Objectives
- Implement pagination for large datasets.
- Implement filtering based on various criteria.
- Implement search functionality to find specific records.
- Integrate these features into appropriate routes and controllers.
Step-by-Step Guide
1. Implement Pagination
Pagination helps in managing large datasets by dividing them into manageable chunks, which can be fetched one page at a time.
- Create Pagination Middleware: Inside the
middlewares
directory, create a file namedpaginationMiddleware.js
.- File:
middlewares/paginationMiddleware.js
- File:
const paginateResults = (model) => {
return async (req, res, next) => {
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
const skip = (page - 1) * limit;
try {
const total = await model.countDocuments();
const results = await model.find().limit(limit).skip(skip);
res.paginatedResults = {
total,
page,
limit,
totalPages: Math.ceil(total / limit),
results,
};
next();
} catch (error) {
res.status(500).json({ message: error.message });
}
};
};
module.exports = paginateResults;
2. Implement Filtering
Filtering allows users to narrow down the list of records based on specific criteria.
- Create Filtering Middleware: Inside the
middlewares
directory, create a file namedfilteringMiddleware.js
.- File:
middlewares/filteringMiddleware.js
- File:
const filterResults = (model) => {
return async (req, res, next) => {
const queryObject = { ...req.query };
const excludedFields = ['page', 'limit', 'sort', 'fields'];
excludedFields.forEach((el) => delete queryObject[el]);
let query = model.find(queryObject);
try {
const results = await query;
res.filteredResults = {
results,
};
next();
} catch (error) {
res.status(500).json({ message: error.message });
}
};
};
module.exports = filterResults;
3. Implement Search Functionality
Search functionality enables users to find specific records based on search terms.
- Create Search Middleware: Inside the
middlewares
directory, create a file namedsearchMiddleware.js
.- File:
middlewares/searchMiddleware.js
- File:
const searchResults = (model, searchableFields) => {
return async (req, res, next) => {
const searchTerm = req.query.search;
if (searchTerm) {
const searchQuery = {
$or: searchableFields.map((field) => ({
[field]: { $regex: searchTerm, $options: 'i' },
})),
};
try {
const results = await model.find(searchQuery);
res.searchResults = {
results,
};
next();
} catch (error) {
res.status(500).json({ message: error.message });
}
} else {
next();
}
};
};
module.exports = searchResults;
4. Integrate Pagination, Filtering, and Search into Routes
We will integrate these functionalities into appropriate routes and controllers.
Update Car Routes:
- File:
routes/carRoutes.js
- File:
const express = require('express');
const {
addCar,
updateCar,
updateRepairStatus,
getCars,
} = require('../controllers/carController');
const { protect, admin, owner, employee } = require('../middlewares/authMiddleware');
const paginateResults = require('../middlewares/paginationMiddleware');
const filterResults = require('../middlewares/filteringMiddleware');
const searchResults = require('../middlewares/searchMiddleware');
const Car = require('../models/Car');
const router = express.Router();
router.post('/add', protect, admin, addCar);
router.put('/update/:id', protect, admin, updateCar);
router.put('/status/:id', protect, employee, updateRepairStatus);
router.get('/', protect, paginateResults(Car), filterResults(Car), searchResults(Car, ['vin', 'licensePlate', 'model']), getCars);
module.exports = router;
Update Car Controller: Modify the getCars
controller to use the results from pagination, filtering, and search middlewares.
- File:
controllers/carController.js
exports.getCars = async (req, res) => {
try {
const cars = res.searchResults ? res.searchResults.results : res.filteredResults ? res.filteredResults.results : res.paginatedResults.results;
res.json({
total: res.paginatedResults ? res.paginatedResults.total : cars.length,
page: res.paginatedResults ? res.paginatedResults.page : 1,
limit: res.paginatedResults ? res.paginatedResults.limit : cars.length,
totalPages: res.paginatedResults ? res.paginatedResults.totalPages : 1,
results: cars,
});
} catch (error) {
res.status(500).json({ message: error.message });
}
};
By completing this module, you have implemented pagination, filtering, and search functionalities for your car repair backend shop application. You created middleware functions to handle these features and integrated them into the appropriate routes and controllers. This setup enhances the user experience by allowing efficient management and retrieval of large datasets, enabling users to navigate through records, apply filters, and perform searches with ease
Module 8: Notification System
In this module, we will implement a notification system to keep customers informed about the status of their vehicle repairs. This includes sending notifications when the repair process starts and when it is completed. We will use Nodemailer for email notifications and a suitable SMS gateway for text messages. This module ensures timely and efficient communication with customers, enhancing the service experience.
Objectives
- Set up email notifications using Nodemailer.
- Set up SMS notifications using a suitable SMS gateway.
- Create models to log notifications.
- Develop controllers to handle sending notifications.
- Integrate notification functionality into relevant operations.
Step-by-Step Guide
1. Set Up Email Notifications Using Nodemailer
Nodemailer is a module for Node.js applications to send emails.
Install Nodemailer: Ensure Nodemailer is installed in your project.
npm install nodemailer
Create Email Utility: Inside the utils
directory, create a file named email.js
.
- File:
utils/email.js
const nodemailer = require('nodemailer');
const dotenv = require('dotenv');
dotenv.config();
const transporter = nodemailer.createTransport({
service: process.env.EMAIL_SERVICE,
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASS,
},
});
const sendEmail = async (options) => {
const mailOptions = {
from: process.env.EMAIL_USER,
to: options.to,
subject: options.subject,
text: options.text,
};
await transporter.sendMail(mailOptions);
};
module.exports = sendEmail;
2. Set Up SMS Notifications Using a Suitable SMS Gateway
You can use services like Twilio or any other SMS gateway. For this example, we will use Twilio.
Install Twilio: Ensure Twilio is installed in your project.
npm install twilio
Create SMS Utility: Inside the utils
directory, create a file named sms.js
.
- 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);
const sendSMS = async (options) => {
await client.messages.create({
body: options.body,
from: process.env.TWILIO_PHONE_NUMBER,
to: options.to,
});
};
module.exports = sendSMS;
3. Create Notification Models
Create models to log notifications and their statuses.
- Create Notification Model: Inside the
models
directory, create a file namedNotification.js
.- File:
models/Notification.js
- File:
const mongoose = require('mongoose');
const NotificationSchema = new mongoose.Schema({
customer: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
car: { type: mongoose.Schema.Types.ObjectId, ref: 'Car', required: true },
type: { type: String, enum: ['email', 'sms'], required: true },
status: { type: String, enum: ['sent', 'failed'], default: 'sent' },
message: { type: String, required: true },
date: { type: Date, default: Date.now },
});
module.exports = mongoose.model('Notification', NotificationSchema);
4. Develop Controllers to Handle Sending Notifications
Create controllers to send notifications when the repair status is updated.
- Create Notification Controller: Inside the
controllers
directory, create a file namednotificationController.js
.- File:
controllers/notificationController.js
- File:
const Notification = require('../models/Notification');
const sendEmail = require('../utils/email');
const sendSMS = require('../utils/sms');
exports.sendNotification = async (type, customer, car, message) => {
try {
if (type === 'email') {
await sendEmail({
to: customer.email,
subject: 'Repair Status Update',
text: message,
});
} else if (type === 'sms') {
await sendSMS({
to: customer.mobile,
body: message,
});
}
const notification = new Notification({
customer: customer._id,
car: car._id,
type,
message,
});
await notification.save();
} catch (error) {
const notification = new Notification({
customer: customer._id,
car: car._id,
type,
status: 'failed',
message,
});
await notification.save();
console.error(`Failed to send ${type} notification: ${error.message}`);
}
};
5. Integrate Notification Functionality into Relevant Operations
Ensure that notifications are sent when the repair status is updated.
- Update Car Controller: Modify the
updateRepairStatus
controller to send notifications.- File:
controllers/carController.js
- File:
const NotificationController = require('./notificationController');
const User = require('../models/User');
const Car = require('../models/Car');
exports.updateRepairStatus = async (req, res) => {
const { id } = req.params;
const { status, description } = req.body;
try {
const car = await Car.findById(id).populate('customer');
if (car) {
car.repairStatus = status;
car.repairLogs.push({ employee: req.user._id, status, description });
await car.save();
const message = `Your car with VIN ${car.vin} is now ${status}. ${description}`;
await NotificationController.sendNotification('email', car.customer, car, message);
await NotificationController.sendNotification('sms', car.customer, car, message);
res.json(car);
} else {
res.status(404).json({ message: 'Car not found' });
}
} catch (error) {
res.status(400).json({ message: error.message });
}
};
By completing this module, you have implemented a notification system for your car repair backend shop application. You set up email notifications using Nodemailer, SMS notifications using Twilio, created models to log notifications, developed controllers to handle sending notifications, and integrated the notification functionality into relevant operations. This setup ensures that customers are informed in a timely manner about the status of their vehicle repairs, enhancing the service experience.
Module 9: Billing system
In this module, we will implement a billing system to handle invoicing and payments for the car repair services. This includes integrating Stripe for payment processing, creating models to store billing information, developing controllers to manage invoices and payments, and ensuring secure handling of payment information.
Objectives
- Integrate Stripe for payment processing.
- Create models to store billing and invoice information.
- Develop controllers to handle invoice generation and payment processing.
- Ensure secure handling of payment information.
Step-by-Step Guide
1. Integrate Stripe for Payment Processing
Stripe is a popular payment processing platform that provides a secure and easy-to-use API for handling payments.
Install Stripe: Ensure Stripe is installed in your project.
npm install stripe
Create Stripe Utility: Inside the utils
directory, create a file named stripe.js
.
- File:
utils/stripe.js
- File:
const Stripe = require('stripe');
const dotenv = require('dotenv');
dotenv.config();
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
module.exports = stripe;
2. Create Billing Models
Create models to store billing and invoice information, including service costs, discounts, and payment statuses.
- Create Invoice Model: Inside the
models
directory, create a file namedInvoice.js
.- File:
models/Invoice.js
- File:
const mongoose = require('mongoose');
const InvoiceSchema = new mongoose.Schema({
customer: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
car: { type: mongoose.Schema.Types.ObjectId, ref: 'Car', required: true },
services: [
{
service: { type: String, required: true },
price: { type: Number, required: true },
},
],
totalPrice: { type: Number, required: true },
discount: { type: Number, default: 0 },
finalPrice: { type: Number, required: true },
status: { type: String, enum: ['unpaid', 'paid'], default: 'unpaid' },
createdAt: { type: Date, default: Date.now },
paidAt: { type: Date },
});
module.exports = mongoose.model('Invoice', InvoiceSchema);
3. Develop Controllers to Handle Invoices and Payments
Create controllers to generate invoices and process payments using Stripe.
- Create Billing Controller: Inside the
controllers
directory, create a file namedbillingController.js
.- File:
controllers/billingController.js
- File:
const Invoice = require('../models/Invoice');
const stripe = require('../utils/stripe');
exports.createInvoice = async (req, res) => {
const { customer, car, services, discount } = req.body;
try {
const totalPrice = services.reduce((total, service) => total + service.price, 0);
const finalPrice = totalPrice - discount;
const invoice = new Invoice({
customer,
car,
services,
totalPrice,
discount,
finalPrice,
});
await invoice.save();
res.status(201).json(invoice);
} catch (error) {
res.status(400).json({ message: error.message });
}
};
exports.payInvoice = async (req, res) => {
const { invoiceId, paymentMethodId } = req.body;
try {
const invoice = await Invoice.findById(invoiceId).populate('customer');
if (!invoice) {
return res.status(404).json({ message: 'Invoice not found' });
}
const paymentIntent = await stripe.paymentIntents.create({
amount: invoice.finalPrice * 100,
currency: 'usd',
payment_method: paymentMethodId,
confirm: true,
});
if (paymentIntent.status === 'succeeded') {
invoice.status = 'paid';
invoice.paidAt = new Date();
await invoice.save();
res.json({ message: 'Payment successful', invoice });
} else {
res.status(400).json({ message: 'Payment failed' });
}
} catch (error) {
res.status(500).json({ message: error.message });
}
};
4. Set Up Routes for Billing
Create routes to handle invoice generation and payment processing, linking them to the corresponding controller methods.
Create Billing Routes: Inside the
routes
directory, create a file namedbillingRoutes.js
.- File:
routes/billingRoutes.js
- File:
const express = require('express');
const { createInvoice, payInvoice } = require('../controllers/billingController');
const { protect, admin } = require('../middlewares/authMiddleware');
const router = express.Router();
router.post('/create', protect, admin, createInvoice);
router.post('/pay', protect, payInvoice);
module.exports = router;
Update app.js
: Import the billing routes and add middleware to handle JSON requests.
- File:
app.js
const express = require('express');
const connectDB = require('./config/db');
const dotenv = require('dotenv');
const authRoutes = require('./routes/authRoutes');
const employeeRoutes = require('./routes/employeeRoutes');
const carRoutes = require('./routes/carRoutes');
const billingRoutes = require('./routes/billingRoutes');
dotenv.config();
connectDB();
const app = express();
app.use(express.json());
app.use('/api/auth', authRoutes);
app.use('/api/employees', employeeRoutes);
app.use('/api/cars', carRoutes);
app.use('/api/billing', billingRoutes);
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
By completing this module, you have implemented a billing system for your car repair backend shop application. You integrated Stripe for payment processing, created models to store billing and invoice information, developed controllers to handle invoice generation and payment processing, and ensured secure handling of payment information. This setup provides a robust billing solution, ensuring smooth and secure transactions for your car repair services.
Module 10: File Upload and Storage
In this module, we will implement a system to allow employees to upload photos of car parts and repairs. This includes setting up Multer for handling file uploads, integrating Cloudinary for storing the uploaded images securely, creating models to associate uploaded images with relevant car repairs and employees, and developing controllers to manage file uploads and retrievals.
Objectives
- Set up Multer for handling file uploads.
- Integrate Cloudinary for storing uploaded images.
- Create models to associate uploaded images with car repairs and employees.
- Develop controllers to manage file uploads and retrievals.
- Ensure secure handling of file uploads and access control.
Step-by-Step Guide
1. Set Up Multer for Handling File Uploads
Multer is a middleware for handling multipart/form-data, which is primarily used for uploading files.
Install Multer: Ensure Multer is installed in your project.
npm install multer
Create Multer Configuration: Inside the middlewares
directory, create a file named uploadMiddleware.js
.
- File:
middlewares/uploadMiddleware.js
const multer = require('multer');
const path = require('path');
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, 'uploads/');
},
filename: (req, file, cb) => {
cb(null, `${file.fieldname}-${Date.now()}${path.extname(file.originalname)}`);
},
});
const fileFilter = (req, file, cb) => {
if (file.mimetype.startsWith('image')) {
cb(null, true);
} else {
cb(new Error('Not an image! Please upload images only.'), false);
}
};
const upload = multer({
storage,
fileFilter,
limits: { fileSize: 1024 * 1024 * 5 }, // 5MB limit
});
module.exports = upload;
2. Integrate Cloudinary for Storing Uploaded Images
Cloudinary is a cloud service that offers a solution to a web application’s entire image management pipeline.
Install Cloudinary: Ensure Cloudinary is installed in your project.
npm install cloudinary multer-storage-cloudinary
Create Cloudinary Configuration: Inside the utils
directory, create a file named cloudinary.js
.
- File:
utils/cloudinary.js
const cloudinary = require('cloudinary').v2;
const { CloudinaryStorage } = require('multer-storage-cloudinary');
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,
});
const storage = new CloudinaryStorage({
cloudinary,
params: {
folder: 'car-repairs',
allowed_formats: ['jpg', 'png', 'jpeg'],
},
});
module.exports = { cloudinary, storage };
3. Create Models to Associate Uploaded Images with Car Repairs and Employees
Create a model to store references to uploaded images, linking them with relevant car repairs and employees.
- Create Image Model: Inside the
models
directory, create a file namedImage.js
.- File:
models/Image.js
- File:
const mongoose = require('mongoose');
const ImageSchema = new mongoose.Schema({
url: { type: String, required: true },
car: { type: mongoose.Schema.Types.ObjectId, ref: 'Car', required: true },
employee: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
description: { type: String },
uploadedAt: { type: Date, default: Date.now },
});
module.exports = mongoose.model('Image', ImageSchema);
4. Develop Controllers to Manage File Uploads and Retrievals
Create controllers to handle the uploading and retrieval of images.
- Create Image Controller: Inside the
controllers
directory, create a file namedimageController.js
.- File:
controllers/imageController.js
- File:
const Image = require('../models/Image');
const { cloudinary } = require('../utils/cloudinary');
exports.uploadImage = async (req, res) => {
try {
const result = await cloudinary.uploader.upload(req.file.path);
const image = new Image({
url: result.secure_url,
car: req.body.car,
employee: req.user._id,
description: req.body.description,
});
await image.save();
res.status(201).json(image);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
exports.getImagesByCar = async (req, res) => {
try {
const images = await Image.find({ car: req.params.carId }).populate('employee');
res.json(images);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
5. Set Up Routes for File Uploads and Retrievals
Create routes to handle the file upload and retrieval operations, linking them to the corresponding controller methods.
Create Image Routes: Inside the
routes
directory, create a file namedimageRoutes.js
.- File:
routes/imageRoutes.js
- File:
const express = require('express');
const { uploadImage, getImagesByCar } = require('../controllers/imageController');
const { protect, employee } = require('../middlewares/authMiddleware');
const upload = require('../middlewares/uploadMiddleware');
const router = express.Router();
router.post('/upload', protect, employee, upload.single('image'), uploadImage);
router.get('/car/:carId', protect, getImagesByCar);
module.exports = router;
Update app.js
: Import the image routes and add middleware to handle JSON requests.
- File:
app.js
const express = require('express');
const connectDB = require('./config/db');
const dotenv = require('dotenv');
const authRoutes = require('./routes/authRoutes');
const employeeRoutes = require('./routes/employeeRoutes');
const carRoutes = require('./routes/carRoutes');
const billingRoutes = require('./routes/billingRoutes');
const imageRoutes = require('./routes/imageRoutes');
dotenv.config();
connectDB();
const app = express();
app.use(express.json());
app.use('/api/auth', authRoutes);
app.use('/api/employees', employeeRoutes);
app.use('/api/cars', carRoutes);
app.use('/api/billing', billingRoutes);
app.use('/api/images', imageRoutes);
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
By completing this module, you have implemented a system for uploading and storing images for your car repair backend shop application. You set up Multer for handling file uploads, integrated Cloudinary for storing uploaded images securely, created models to associate uploaded images with relevant car repairs and employees, developed controllers to manage file uploads and retrievals, and ensured secure handling of file uploads and access control. This setup allows employees to upload and manage images efficiently, enhancing the overall service experience for customers.
Module 11: Admin Area
In this module, we will develop an admin area to manage the entire application. This includes creating routes and controllers for admin-specific functionalities, such as viewing detailed records of customers, employees, and repairs, as well as generating insights into shop performance. This module ensures that the admin can efficiently oversee and manage all aspects of the repair shop.
Objectives
- Create routes and controllers for admin-specific functionalities.
- Implement data aggregation features to provide insights into shop performance.
- Ensure that only admin users have access to these routes and functionalities.
Step-by-Step Guide
1. Create Admin Controllers
Controllers handle the business logic for admin-specific operations. We will create controllers to fetch detailed records and generate reports.
- Create Admin Controller: Inside the
controllers
directory, create a file namedadminController.js
.- File:
controllers/adminController.js
- File:
const User = require('../models/User');
const Car = require('../models/Car');
const Invoice = require('../models/Invoice');
exports.getAllCustomers = async (req, res) => {
try {
const customers = await User.find({ role: 'owner' });
res.json(customers);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
exports.getAllEmployees = async (req, res) => {
try {
const employees = await User.find({ role: 'employee' });
res.json(employees);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
exports.getEmployeeRecords = async (req, res) => {
try {
const employeeId = req.params.id;
const carsRepaired = await Car.find({ 'repairLogs.employee': employeeId });
res.json(carsRepaired);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
exports.getShopPerformance = async (req, res) => {
try {
const totalCustomers = await User.countDocuments({ role: 'owner' });
const totalEmployees = await User.countDocuments({ role: 'employee' });
const totalCars = await Car.countDocuments();
const totalInvoices = await Invoice.countDocuments();
const totalRevenue = await Invoice.aggregate([
{ $match: { status: 'paid' } },
{ $group: { _id: null, total: { $sum: '$finalPrice' } } },
]);
res.json({
totalCustomers,
totalEmployees,
totalCars,
totalInvoices,
totalRevenue: totalRevenue[0]?.total || 0,
});
} catch (error) {
res.sta
2. Set Up Routes for Admin Area
Create routes to handle admin-specific operations, linking them to the corresponding controller methods.
- Create Admin Routes: Inside the
routes
directory, create a file namedadminRoutes.js
.- File:
routes/adminRoutes.js
- File:
const express = require('express');
const {
getAllCustomers,
getAllEmployees,
getEmployeeRecords,
getShopPerformance,
} = require('../controllers/adminController');
const { protect, admin } = require('../middlewares/authMiddleware');
const router = express.Router();
router.get('/customers', protect, admin, getAllCustomers);
router.get('/employees', protect, admin, getAllEmployees);
router.get('/employee-records/:id', protect, admin, getEmployeeRecords);
router.get('/performance', protect, admin, getShopPerformance);
module.exports = router;
3. Apply Middleware to Protect Routes
Ensure that the routes for the admin area are protected and can only be accessed by admin users.
- Update
app.js
: Import the admin routes and add middleware to handle JSON requests.- File:
app.js
- File:
const express = require('express');
const connectDB = require('./config/db');
const dotenv = require('dotenv');
const authRoutes = require('./routes/authRoutes');
const employeeRoutes = require('./routes/employeeRoutes');
const carRoutes = require('./routes/carRoutes');
const billingRoutes = require('./routes/billingRoutes');
const imageRoutes = require('./routes/imageRoutes');
const adminRoutes = require('./routes/adminRoutes');
dotenv.config();
connectDB();
const app = express();
app.use(express.json());
app.use('/api/auth', authRoutes);
app.use('/api/employees', employeeRoutes);
app.use('/api/cars', carRoutes);
app.use('/api/billing', billingRoutes);
app.use('/api/images', imageRoutes);
app.use('/api/admin', adminRoutes);
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
By completing this module, you have developed an admin area for your car repair backend shop application. You created routes and controllers for admin-specific functionalities, implemented data aggregation features to provide insights into shop performance, and ensured that only admin users have access to these routes and functionalities. This setup allows the admin to efficiently oversee and manage all aspects of the repair shop, contributing to better organizational management and workflow.
Module 12: Visits and Offers Management
In this module, we will implement a system to manage customer visits and service offers. This includes creating models to store visit and offer information, developing controllers to handle visit and offer management, and setting up routes to facilitate these operations. This module ensures that customer interactions are efficiently tracked and managed, providing a seamless service experience.
Objectives
- Create models to store visit and offer information.
- Develop controllers to handle visit and offer management.
- Set up routes to facilitate visit and offer operations.
- Ensure accurate and up-to-date information on customer visits and service offers.
Step-by-Step Guide
1. Create Visit and Offer Models
Models represent the structure of visit and offer data in the MongoDB database.
Create Visit Model: Inside the
models
directory, create a file namedVisit.js
.- File:
models/Visit.js
- File:
const mongoose = require('mongoose');
const VisitSchema = new mongoose.Schema({
repairShop: { type: mongoose.Schema.Types.ObjectId, ref: 'RepairShop', required: true },
customer: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
vehicle: { type: mongoose.Schema.Types.ObjectId, ref: 'Car', required: true },
visitStartDate: { type: Date, required: true },
visitStartTime: { type: String, required: true },
visitEndDate: { type: Date },
visitEndTime: { type: String },
licensePlate: { type: String, required: true },
offer: { type: mongoose.Schema.Types.ObjectId, ref: 'Offer' },
serviceCatalog: { type: mongoose.Schema.Types.ObjectId, ref: 'ServiceCatalog' },
serviceDiscount: { type: Number },
visitPrice: { type: Number, required: true },
invoiceCreated: { type: Date },
invoiceDue: { type: Date },
invoiceCharged: { type: Date },
insertTs: { type: Date, default: Date.now },
});
module.exports = mongoose.model('Visit', VisitSchema);
Create Offer Model: Inside the models
directory, create a file named Offer.js
.
- File:
models/Offer.js
const mongoose = require('mongoose');
const OfferSchema = new mongoose.Schema({
customer: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
contact: { type: mongoose.Schema.Types.ObjectId, ref: 'Contact' },
offerDescription: { type: String },
serviceCatalog: { type: mongoose.Schema.Types.ObjectId, ref: 'ServiceCatalog' },
serviceDiscount: { type: Number },
offerPrice: { type: Number, required: true },
insertTs: { type: Date, default: Date.now },
});
module.exports = mongoose.model('Offer', OfferSchema);
2. Develop Visit and Offer Controllers
Controllers handle the business logic for managing visits and offers.
Create Visit Controller: Inside the
controllers
directory, create a file namedvisitController.js
.- File:
controllers/visitController.js
- File:
const Visit = require('../models/Visit');
exports.createVisit = async (req, res) => {
const { repairShop, customer, vehicle, visitStartDate, visitStartTime, licensePlate, offer, serviceCatalog, serviceDiscount, visitPrice } = req.body;
try {
const visit = new Visit({
repairShop,
customer,
vehicle,
visitStartDate,
visitStartTime,
licensePlate,
offer,
serviceCatalog,
serviceDiscount,
visitPrice,
});
await visit.save();
res.status(201).json(visit);
} catch (error) {
res.status(400).json({ message: error.message });
}
};
exports.getVisits = async (req, res) => {
try {
const visits = await Visit.find().populate('repairShop customer vehicle offer serviceCatalog');
res.json(visits);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
exports.updateVisit = async (req, res) => {
const { id } = req.params;
const { visitEndDate, visitEndTime, invoiceCreated, invoiceDue, invoiceCharged } = req.body;
try {
const visit = await Visit.findById(id);
if (visit) {
visit.visitEndDate = visitEndDate || visit.visitEndDate;
visit.visitEndTime = visitEndTime || visit.visitEndTime;
visit.invoiceCreated = invoiceCreated || visit.invoiceCreated;
visit.invoiceDue = invoiceDue || visit.invoiceDue;
visit.invoiceCharged = invoiceCharged || visit.invoiceCharged;
await visit.save();
res.json(visit);
} else {
res.status(404).json({ message: 'Visit not found' });
}
} catch (error) {
res.status(400).json({ message: error.message });
}
};
Create Offer Controller: Inside the controllers
directory, create a file named offerController.js
.
- File:
controllers/offerController.js
const Offer = require('../models/Offer');
exports.createOffer = async (req, res) => {
const { customer, contact, offerDescription, serviceCatalog, serviceDiscount, offerPrice } = req.body;
try {
const offer = new Offer({
customer,
contact,
offerDescription,
serviceCatalog,
serviceDiscount,
offerPrice,
});
await offer.save();
res.status(201).json(offer);
} catch (error) {
res.status(400).json({ message: error.message });
}
};
exports.getOffers = async (req, res) => {
try {
const offers = await Offer.find().populate('customer contact serviceCatalog');
res.json(offers);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
exports.updateOffer = async (req, res) => {
const { id } = req.params;
const { offerDescription, serviceDiscount, offerPrice } = req.body;
try {
const offer = await Offer.findById(id);
if (offer) {
offer.offerDescription = offerDescription || offer.offerDescription;
offer.serviceDiscount = serviceDiscount || offer.serviceDiscount;
offer.offerPrice = offerPrice || offer.offerPrice;
await offer.save();
res.json(offer);
} else {
res.status(404).json({ message: 'Offer not found' });
}
} catch (error) {
res.status(400).json({ message: error.message });
}
};
3. Set Up Routes for Visits and Offers
Create routes to handle visit and offer operations, linking them to the corresponding controller methods.
Create Visit Routes: Inside the
routes
directory, create a file namedvisitRoutes.js
.- File:
routes/visitRoutes.js
- File:
const express = require('express');
const { createVisit, getVisits, updateVisit } = require('../controllers/visitController');
const { protect, admin, owner } = require('../middlewares/authMiddleware');
const router = express.Router();
router.post('/create', protect, owner, createVisit);
router.get('/', protect, admin, getVisits);
router.put('/update/:id', protect, admin, updateVisit);
module.exports = router;
Create Offer Routes: Inside the routes
directory, create a file named offerRoutes.js
.
- File:
routes/offerRoutes.js
const express = require('express');
const { createOffer, getOffers, updateOffer } = require('../controllers/offerController');
const { protect, admin, owner } = require('../middlewares/authMiddleware');
const router = express.Router();
router.post('/create', protect, owner, createOffer);
router.get('/', protect, admin, getOffers);
router.put('/update/:id', protect, admin, updateOffer);
module.exports = router;
Update app.js
: Import the visit and offer routes and add middleware to handle JSON requests.
- File:
app.js
const express = require('express');
const connectDB = require('./config/db');
const dotenv = require('dotenv');
const authRoutes = require('./routes/authRoutes');
const employeeRoutes = require('./routes/employeeRoutes');
const carRoutes = require('./routes/carRoutes');
const billingRoutes = require('./routes/billingRoutes');
const imageRoutes = require('./routes/imageRoutes');
const adminRoutes = require('./routes/adminRoutes');
const visitRoutes = require('./routes/visitRoutes');
const offerRoutes = require('./routes/offerRoutes');
dotenv.config();
connectDB();
const app = express();
app.use(express.json());
app.use('/api/auth', authRoutes);
app.use('/api/employees', employeeRoutes);
app.use('/api/cars', carRoutes);
app.use('/api/billing', billingRoutes);
app.use('/api/images', imageRoutes);
app.use('/api/admin', adminRoutes);
app.use('/api/visits', visitRoutes);
app.use('/api/offers', offerRoutes);
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
By completing this module, you have implemented a system for managing customer visits and service offers for your car repair backend shop application. You created models to store visit and offer information, developed controllers to handle visit and offer management, set up routes to facilitate these operations, and ensured accurate and up-to-date information on customer interactions. This setup allows for efficient tracking and management of customer visits and service offers, enhancing the overall service experience.
Module 13: Testing and Deployment
In this final module, we will focus on testing the application to ensure it works correctly and is ready for deployment. We will use a testing framework to write unit tests for critical components and perform integration testing. Finally, we will prepare the application for deployment, ensuring it is secure and ready for production.
Objectives
- Write unit tests for critical components using a testing framework.
- Perform integration testing to ensure all modules work seamlessly together.
- Set up a deployment pipeline using tools like Docker, Heroku, or AWS.
- Ensure secure handling of environment variables and configurations in the production environment.
Step-by-Step Guide
1. Write Unit Tests for Critical Components
Unit tests ensure that individual components of the application work as expected.
Install Testing Framework: Ensure Mocha and Chai are installed in your project.
npm install mocha chai chai-http --save-dev
Create Test Directory: Create a
test
directory in the root of your project to store test files.Write Unit Tests: Inside the
test
directory, create test files for different components.- File:
test/user.test.js
- File:
const chai = require('chai');
const chaiHttp = require('chai-http');
const server = require('../app');
const User = require('../models/User');
chai.should();
chai.use(chaiHttp);
describe('Users', () => {
before((done) => {
User.deleteMany({}, (err) => {
done();
});
});
describe('/POST register', () => {
it('it should register a new user', (done) => {
const user = {
firstName: 'John',
lastName: 'Doe',
email: 'john@example.com',
password: '123456',
role: 'admin'
};
chai.request(server)
.post('/api/auth/register')
.send(user)
.end((err, res) => {
res.should.have.status(201);
res.body.should.be.a('object');
res.body.should.have.property('token');
done();
});
});
});
describe('/POST login', () => {
it('it should login an existing user', (done) => {
const user = {
email: 'john@example.com',
password: '123456'
};
chai.request(server)
.post('/api/auth/login')
.send(user)
.end((err, res) => {
res.should.have.status(200);
res.body.should.be.a('object');
res.body.should.have.property('token');
done();
});
});
});
});
2. Perform Integration Testing
Integration tests ensure that different modules of the application work together as expected.
- Write Integration Tests: Inside the
test
directory, create integration test files.- File:
test/integration.test.js
- File:
const chai = require('chai');
const chaiHttp = require('chai-http');
const server = require('../app');
chai.should();
chai.use(chaiHttp);
describe('Integration Tests', () => {
it('it should GET all cars', (done) => {
chai.request(server)
.get('/api/cars')
.end((err, res) => {
res.should.have.status(200);
res.body.should.be.a('array');
done();
});
});
it('it should CREATE a new car', (done) => {
const car = {
vin: '1HGCM82633A123456',
licensePlate: 'ABC123',
customer: 'customerId',
model: 'Honda Accord',
manufacturedYear: 2020,
manufacturedMonth: 6,
details: 'Some details about the car'
};
chai.request(server)
.post('/api/cars/add')
.send(car)
.end((err, res) => {
res.should.have.status(201);
res.body.should.be.a('object');
res.body.should.have.property('vin');
done();
});
});
});
3. Set Up a Deployment Pipeline
Prepare the application for deployment using a tool like Docker, Heroku, or AWS.
Create a Dockerfile: Create a
Dockerfile
in the root directory to containerize the application.- File:
Dockerfile
- File:
FROM node:14
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 5000
CMD ["node", "app.js"]
Create a Docker Compose File: Create a docker-compose.yml
file to manage multi-container applications.
- File:
docker-compose.yml
version: '3'
services:
app:
build: .
ports:
- "5000:5000"
environment:
- MONGO_URI=mongodb://mongo:27017/car-repair-shop
- JWT_SECRET=your_jwt_secret
- CLOUDINARY_URL=your_cloudinary_url
- STRIPE_SECRET_KEY=your_stripe_secret_key
- EMAIL_SERVICE=your_email_service
- EMAIL_USER=your_email_user
- EMAIL_PASS=your_email_pass
depends_on:
- mongo
mongo:
image: mongo:latest
ports:
- "27017:27017"
Deploy to Heroku: If using Heroku, follow their deployment guides. Ensure environment variables are set in the Heroku dashboard.
4. Ensure Secure Handling of Environment Variables
Use dotenv for Local Development: Ensure
.env
file is properly configured and not checked into version control.Configure Environment Variables in Production: Set environment variables securely in your production environment (e.g., using AWS Secrets Manager, Heroku config vars).
By completing this module, you have tested and prepared your car repair backend shop application for deployment. You wrote unit tests for critical components using a testing framework, performed integration testing to ensure all modules work seamlessly together, set up a deployment pipeline using Docker and Docker Compose, and ensured secure handling of environment variables and configurations in the production environment. This setup ensures that your application is robust, secure, and ready for production use.
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.