In this article, we will explore how to build a backend application for a car repair shop using Node.js, Express, and MongoDB. This app will be structured using a 3-layer architecture to ensure a clean separation of concerns, promoting maintainability and scalability. Our aim is to encapsulate business logic in dedicated classes, keeping it separate from the API routes and controllers.
Overview
Business Idea
The car repair shop app will handle various aspects of running a car repair business. It will manage repair shops, employees, customers, vehicles, services, and visits. Key functionalities include:
- Employee registration and login (handled by the admin).
- Role-based access control (admin, owner, employee).
- Vehicle repair status tracking.
- Notifications for repair start and completion.
- Billing and invoice management using Stripe.
- File uploads for parts and car photos using Multer and Cloudinary.
- A comprehensive admin area for detailed reports and management.
- Pagination, filtering, and search functionalities for efficient data handling.
Architecture
We will adopt a 3-layer architecture to ensure a clear separation of concerns:
- Routes: Define the API endpoints.
- Controllers: Handle incoming requests and responses.
- Services (Business Logic): Encapsulate the core business logic in dedicated classes.
This structure will help us avoid the pitfalls of spaghetti code, making the application easier to test and maintain.
Modules
We will break down the application into several modules, each focusing on a specific aspect of the car repair shop operations. Each module will be developed step-by-step to ensure clarity and coherence.
Initial Setup
The initial setup involves setting up the project directory, initializing a Node.js project, and installing necessary dependencies. We will configure the MongoDB connection and set up the basic directory structure for our project.
Employee Management Module
This module will handle employee registration, login, and management. The admin will be responsible for registering employees and assigning roles. We will implement authentication using bcrypt for password hashing and JWT for token-based authentication.
Admin Area Module
The admin area will provide functionalities for managing the entire system. Admins will be able to view detailed reports on customers, employees, vehicles, services, and visits. This module will also include role-based access control to ensure only authorized users can access specific functionalities.
Customer Management Module
This module will manage customer details and contacts. We will store customer information, track their interactions with the repair shop, and maintain a history of their visits and services.
Vehicle Management Module
In this module, we will manage vehicle details, including make, model, and owner information. Each vehicle will be associated with a customer, and we will track the history of repairs and services performed on each vehicle.
Service and Offer Management Module
This module will define the services and tasks offered by the repair shop. We will manage a catalog of services and tasks, create offers for customers, and track the acceptance and completion of these offers.
Visit Management Module
The visit management module will track customer visits to the repair shop. We will store details of each visit, including the tasks performed and the final invoice. This module will also handle scheduling and tracking the status of repairs.
Notification System Module
We will implement a notification system to send alerts to customers and employees. Notifications will be sent via SMS and email using Nodemailer. Key events such as the start and completion of repairs will trigger notifications.
Billing System Module
The billing system will handle payment processing using Stripe. We will generate invoices for completed services and track payment statuses. This module will also include functionalities for managing discounts and promotions.
Pagination, Filtering, and Search Module
To handle large datasets efficiently, we will implement pagination, filtering, and search functionalities. This module will ensure that users can easily navigate and find relevant information within the system.
Deployment and Testing
Finally, we will cover the deployment and testing of our application. We will set up the deployment process, including environment configuration and CI/CD pipelines. Testing will involve unit tests for our business logic and integration tests for the overall system.
Module 1: Initial Setup
In this module, we will set up the foundational structure for our car repair shop backend application. This includes creating the project directory, initializing the Node.js project, installing necessary dependencies, configuring the MongoDB connection, and setting up the basic directory structure.
Step 1: Create Project Directory
First, we need to create a directory for our project. Open your terminal and run the following commands:
mkdir car-repair-shop
cd car-repair-shop
Step 2: Initialize Node.js Project
Initialize a new Node.js project by running the following command. This will create a package.json
file to manage project dependencies.
npm init -y
Step 3: Install Dependencies
We need to install several dependencies to build our application. These include Express for server-side framework, Mongoose for MongoDB object modeling, bcrypt for password hashing, JWT for authentication, cookie-parser for handling cookies, Nodemailer for sending emails, Multer and Cloudinary for file uploads, and Stripe for payment processing.
Run the following command to install the dependencies:
npm install express mongoose bcryptjs jsonwebtoken cookie-parser nodemailer multer cloudinary stripe
Step 4: Create Directory Structure
Next, we will create a directory structure to organize our code. This helps in maintaining a clean and manageable codebase. Run the following commands to create the necessary directories:
mkdir src
cd src
mkdir config controllers models routes services utils
Step 5: Environment Configuration
Create a .env
file in the root directory to store environment variables such as MongoDB URI, JWT secret, and other sensitive information. The .env
file might look like this:
MONGO_URI=mongodb://localhost:27017/car-repair-shop
JWT_SECRET=your_jwt_secret_key
CLOUDINARY_CLOUD_NAME=your_cloudinary_cloud_name
CLOUDINARY_API_KEY=your_cloudinary_api_key
CLOUDINARY_API_SECRET=your_cloudinary_api_secret
STRIPE_SECRET_KEY=your_stripe_secret_key
EMAIL_HOST=smtp.your-email-provider.com
EMAIL_PORT=587
EMAIL_USER=your_email@example.com
EMAIL_PASS=your_email_password
Step 6: MongoDB Connection
We need to set up the connection to MongoDB. Create a file named db.js
in the config
directory with the following content:
const mongoose = require('mongoose');
const connectDB = async () => {
try {
await mongoose.connect(process.env.MONGO_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
console.log('MongoDB connected');
} catch (error) {
console.error('Error connecting to MongoDB:', error);
process.exit(1);
}
};
module.exports = connectDB;
Step 7: Express Server Setup
Create a file named server.js
in the root directory with the following content. This will set up our Express server and connect to MongoDB:
require('dotenv').config();
const express = require('express');
const connectDB = require('./src/config/db');
const app = express();
// Connect to MongoDB
connectDB();
// Middleware
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(require('cookie-parser')());
// Routes
app.use('/api/employees', require('./src/routes/employeeRoutes'));
// Add other routes as needed
// Error handling middleware
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('Something broke!');
});
// Start the server
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
Step 8: Basic Routes and Controllers
To keep our setup complete, let’s add a basic route and controller for employee management. Create a file named employeeRoutes.js
in the routes
directory with the following content:
const express = require('express');
const router = express.Router();
const employeeController = require('../controllers/employeeController');
router.post('/register', employeeController.register);
router.post('/login', employeeController.login);
module.exports = router;
Next, create the employeeController.js
in the controllers
directory:
const EmployeeService = require('../services/employeeService');
exports.register = async (req, res, next) => {
try {
const result = await EmployeeService.register(req.body);
res.status(201).json(result);
} catch (error) {
next(error);
}
};
exports.login = async (req, res, next) => {
try {
const result = await EmployeeService.login(req.body);
res.status(200).json(result);
} catch (error) {
next(error);
}
};
Finally, create a basic employeeService.js
in the services
directory:
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const Employee = require('../models/Employee');
class EmployeeService {
static async register(data) {
const { firstName, lastName, email, password } = data;
const hashedPassword = await bcrypt.hash(password, 10);
const employee = new Employee({
firstName,
lastName,
email,
password: hashedPassword,
});
await employee.save();
return { message: 'Employee registered successfully' };
}
static async login(data) {
const { email, password } = data;
const employee = await Employee.findOne({ email });
if (!employee) {
throw new Error('Invalid credentials');
}
const isMatch = await bcrypt.compare(password, employee.password);
if (!isMatch) {
throw new Error('Invalid credentials');
}
const token = jwt.sign({ id: employee._id }, process.env.JWT_SECRET, {
expiresIn: '1h',
});
return { token };
}
}
module.exports = EmployeeService;
Step 9: Employee Model
Create the Employee.js
model in the models
directory:
const mongoose = require('mongoose');
const employeeSchema = 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, default: 'employee' },
employmentStartDate: { type: Date, default: Date.now },
employmentEndDate: { type: Date, default: null },
isActive: { type: Boolean, default: true },
}, { timestamps: true });
module.exports = mongoose.model('Employee', employeeSchema);
Module 2: Employee Management Module
In this module, we will expand on the employee management functionalities. We will implement additional features such as viewing all employees, updating employee details, and deactivating employees. We will also enforce role-based access control to ensure that only admins can perform certain actions.
Step 1: Update Employee Routes
First, we need to add new routes for the additional functionalities. Update employeeRoutes.js
in the routes
directory:
With this setup, we have established the foundational structure for our car repair shop backend application. We’ve configured our project, connected to MongoDB, and set up the Express server. We’ve also created basic routes, controllers, services, and models to handle employee registration and login. In the next module, we will delve deeper into employee management, adding more functionalities and refining our initial setup.
const express = require('express');
const router = express.Router();
const employeeController = require('../controllers/employeeController');
const authMiddleware = require('../middleware/authMiddleware');
router.post('/register', authMiddleware.verifyAdmin, employeeController.register);
router.post('/login', employeeController.login);
router.get('/', authMiddleware.verifyAdmin, employeeController.getAllEmployees);
router.put('/:id', authMiddleware.verifyAdmin, employeeController.updateEmployee);
router.delete('/:id', authMiddleware.verifyAdmin, employeeController.deactivateEmployee);
module.exports = router;
Step 2: Update Employee Controller
Next, update the employeeController.js
in the controllers
directory to handle the new functionalities:
const EmployeeService = require('../services/employeeService');
exports.register = async (req, res, next) => {
try {
const result = await EmployeeService.register(req.body);
res.status(201).json(result);
} catch (error) {
next(error);
}
};
exports.login = async (req, res, next) => {
try {
const result = await EmployeeService.login(req.body);
res.status(200).json(result);
} catch (error) {
next(error);
}
};
exports.getAllEmployees = async (req, res, next) => {
try {
const result = await EmployeeService.getAllEmployees();
res.status(200).json(result);
} catch (error) {
next(error);
}
};
exports.updateEmployee = async (req, res, next) => {
try {
const result = await EmployeeService.updateEmployee(req.params.id, req.body);
res.status(200).json(result);
} catch (error) {
next(error);
}
};
exports.deactivateEmployee = async (req, res, next) => {
try {
const result = await EmployeeService.deactivateEmployee(req.params.id);
res.status(200).json(result);
} catch (error) {
next(error);
}
};
Step 3: Employee Service Class
Update the employeeService.js
in the services
directory to include the new methods:
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const Employee = require('../models/Employee');
class EmployeeService {
static async register(data) {
const { firstName, lastName, email, password, role } = data;
const hashedPassword = await bcrypt.hash(password, 10);
const employee = new Employee({
firstName,
lastName,
email,
password: hashedPassword,
role,
});
await employee.save();
return { message: 'Employee registered successfully' };
}
static async login(data) {
const { email, password } = data;
const employee = await Employee.findOne({ email });
if (!employee) {
throw new Error('Invalid credentials');
}
const isMatch = await bcrypt.compare(password, employee.password);
if (!isMatch) {
throw new Error('Invalid credentials');
}
const token = jwt.sign({ id: employee._id, role: employee.role }, process.env.JWT_SECRET, {
expiresIn: '1h',
});
return { token };
}
static async getAllEmployees() {
const employees = await Employee.find().select('-password');
return employees;
}
static async updateEmployee(id, data) {
const { firstName, lastName, email, role, isActive } = data;
const employee = await Employee.findById(id);
if (!employee) {
throw new Error('Employee not found');
}
employee.firstName = firstName || employee.firstName;
employee.lastName = lastName || employee.lastName;
employee.email = email || employee.email;
employee.role = role || employee.role;
employee.isActive = isActive !== undefined ? isActive : employee.isActive;
await employee.save();
return { message: 'Employee updated successfully' };
}
static async deactivateEmployee(id) {
const employee = await Employee.findById(id);
if (!employee) {
throw new Error('Employee not found');
}
employee.isActive = false;
await employee.save();
return { message: 'Employee deactivated successfully' };
}
}
module.exports = EmployeeService;
Step 4: Authentication Middleware
Create a middleware to handle authentication and role-based access control. Create a file named authMiddleware.js
in the middleware
directory:
const jwt = require('jsonwebtoken');
const verifyToken = (req, res, next) => {
const token = req.headers.authorization?.split(' ')[1];
if (!token) {
return res.status(401).json({ message: 'Access denied. No token provided.' });
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
res.status(400).json({ message: 'Invalid token.' });
}
};
const verifyAdmin = (req, res, next) => {
verifyToken(req, res, () => {
if (req.user.role !== 'admin') {
return res.status(403).json({ message: 'Access denied. Admins only.' });
}
next();
});
};
module.exports = {
verifyToken,
verifyAdmin,
};
Step 5: Update Employee Model
Add the role
field to the Employee
model if it hasn’t been added already:
const mongoose = require('mongoose');
const employeeSchema = 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, default: 'employee' },
employmentStartDate: { type: Date, default: Date.now },
employmentEndDate: { type: Date, default: null },
isActive: { type: Boolean, default: true },
}, { timestamps: true });
module.exports = mongoose.model('Employee', employeeSchema);
Step 6: Testing the Implementation
Make sure to test the new functionalities:
- Register a new employee (as an admin).
- Login with the employee credentials.
- Get all employees (as an admin).
- Update employee details (as an admin).
- Deactivate an employee (as an admin).
Testing with Postman or similar tool:
Register: POST
http://localhost:5000/api/employees/register
{
"firstName": "John",
"lastName": "Doe",
"email": "john.doe@example.com",
"password": "password123",
"role": "employee"
}
Login: POST http://localhost:5000/api/employees/login
{
"email": "john.doe@example.com",
"password": "password123"
}
et All Employees: GET
http://localhost:5000/api/employees/
- Headers:
Authorization: Bearer <token>
- Headers:
Update Employee: PUT
http://localhost:5000/api/employees/:id
{
"firstName": "Jane",
"lastName": "Doe",
"email": "jane.doe@example.com",
"role": "admin",
"isActive": true
}
Deactivate Employee: DELETE
http://localhost:5000/api/employees/:id
- Headers:
Authorization: Bearer <token>
- Headers:
In this module, we expanded the employee management functionalities to include viewing all employees, updating employee details, and deactivating employees. We also implemented role-based access control to ensure only admins can perform certain actions. This completes the employee management module, making it robust and secure. In the next module, we will focus on building the admin area functionalities.
Module 3: Admin Area Module
In this module, we will build the admin area functionalities. This includes viewing detailed reports on customers, employees, vehicles, services, and visits. Admins will have the ability to manage the entire system and perform administrative tasks.
Step 1: Admin Routes
Create routes for the admin area functionalities. Create a file named adminRoutes.js
in the routes
directory:
const express = require('express');
const router = express.Router();
const adminController = require('../controllers/adminController');
const authMiddleware = require('../middleware/authMiddleware');
// Define admin routes
router.get('/dashboard', authMiddleware.verifyAdmin, adminController.getDashboard);
router.get('/reports/customers', authMiddleware.verifyAdmin, adminController.getCustomerReport);
router.get('/reports/employees', authMiddleware.verifyAdmin, adminController.getEmployeeReport);
router.get('/reports/vehicles', authMiddleware.verifyAdmin, adminController.getVehicleReport);
router.get('/reports/services', authMiddleware.verifyAdmin, adminController.getServiceReport);
router.get('/reports/visits', authMiddleware.verifyAdmin, adminController.getVisitReport);
module.exports = router;
Step 2: Admin Controller
Create the adminController.js
in the controllers
directory to handle admin-specific requests:
const AdminService = require('../services/adminService');
exports.getDashboard = async (req, res, next) => {
try {
const dashboardData = await AdminService.getDashboardData();
res.status(200).json(dashboardData);
} catch (error) {
next(error);
}
};
exports.getCustomerReport = async (req, res, next) => {
try {
const customerReport = await AdminService.getCustomerReport();
res.status(200).json(customerReport);
} catch (error) {
next(error);
}
};
exports.getEmployeeReport = async (req, res, next) => {
try {
const employeeReport = await AdminService.getEmployeeReport();
res.status(200).json(employeeReport);
} catch (error) {
next(error);
}
};
exports.getVehicleReport = async (req, res, next) => {
try {
const vehicleReport = await AdminService.getVehicleReport();
res.status(200).json(vehicleReport);
} catch (error) {
next(error);
}
};
exports.getServiceReport = async (req, res, next) => {
try {
const serviceReport = await AdminService.getServiceReport();
res.status(200).json(serviceReport);
} catch (error) {
next(error);
}
};
exports.getVisitReport = async (req, res, next) => {
try {
const visitReport = await AdminService.getVisitReport();
res.status(200).json(visitReport);
} catch (error) {
next(error);
}
};
Step 3: Admin Service Class
Create the adminService.js
in the services
directory to encapsulate the business logic for admin operations:
const Employee = require('../models/Employee');
const Customer = require('../models/Customer');
const Vehicle = require('../models/Vehicle');
const Service = require('../models/Service');
const Visit = require('../models/Visit');
class AdminService {
static async getDashboardData() {
const employeeCount = await Employee.countDocuments();
const customerCount = await Customer.countDocuments();
const vehicleCount = await Vehicle.countDocuments();
const serviceCount = await Service.countDocuments();
const visitCount = await Visit.countDocuments();
return {
employeeCount,
customerCount,
vehicleCount,
serviceCount,
visitCount,
};
}
static async getCustomerReport() {
const customers = await Customer.find();
return customers;
}
static async getEmployeeReport() {
const employees = await Employee.find();
return employees;
}
static async getVehicleReport() {
const vehicles = await Vehicle.find();
return vehicles;
}
static async getServiceReport() {
const services = await Service.find();
return services;
}
static async getVisitReport() {
Step 4: Additional Models
Ensure you have the models for Customer
, Vehicle
, Service
, and Visit
. Here are basic examples:
Customer Model
Create the Customer.js
in the models
directory:
const mongoose = require('mongoose');
const customerSchema = new mongoose.Schema({
firstName: { type: String, required: true },
lastName: { type: String, required: true },
email: { type: String, required: true, unique: true },
phone: { type: String, required: true },
address: { type: String, required: true },
createdAt: { type: Date, default: Date.now },
});
module.exports = mongoose.model('Customer', customerSchema);
Vehicle Model
Create the Vehicle.js
in the models
directory:
const mongoose = require('mongoose');
const vehicleSchema = new mongoose.Schema({
vin: { type: String, required: true, unique: true },
licensePlate: { type: String, required: true, unique: true },
customer: { type: mongoose.Schema.Types.ObjectId, ref: 'Customer', required: true },
make: { type: String, required: true },
model: { type: String, required: true },
year: { type: Number, required: true },
details: { type: String },
createdAt: { type: Date, default: Date.now },
});
module.exports = mongoose.model('Vehicle', vehicleSchema);
Service Model
Create the Service.js
in the models
directory:
const mongoose = require('mongoose');
const serviceSchema = new mongoose.Schema({
serviceName: { type: String, required: true },
description: { type: String },
price: { type: Number, required: true },
isActive: { type: Boolean, default: true },
createdAt: { type: Date, default: Date.now },
});
module.exports = mongoose.model('Service', serviceSchema);
Visit Model
Create the Visit.js
in the models
directory:
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: 'Customer', required: true },
vehicle: { type: mongoose.Schema.Types.ObjectId, ref: 'Vehicle', required: true },
startDate: { type: Date, required: true },
endDate: { type: Date },
status: { type: String, required: true },
totalPrice: { type: Number, required: true },
createdAt: { type: Date, default: Date.now },
});
module.exports = mongoose.model('Visit', visitSchema);
Step 5: Integrate Admin Routes
Ensure you include the admin routes in your main server file (server.js
):
// In your server.js
app.use('/api/admin', require('./src/routes/adminRoutes'));
Step 6: Testing the Implementation
Make sure to test the new functionalities:
- Get Dashboard Data: GET
http://localhost:5000/api/admin/dashboard
- Get Customer Report: GET
http://localhost:5000/api/admin/reports/customers
- Get Employee Report: GET
http://localhost:5000/api/admin/reports/employees
- Get Vehicle Report: GET
http://localhost:5000/api/admin/reports/vehicles
- Get Service Report: GET
http://localhost:5000/api/admin/reports/services
- Get Visit Report: GET
http://localhost:5000/api/admin/reports/visits
Testing with Postman or similar tool:
Get Dashboard Data: GET
http://localhost:5000/api/admin/dashboard
- Headers:
Authorization: Bearer <token>
- Headers:
Get Customer Report: GET
http://localhost:5000/api/admin/reports/customers
- Headers:
Authorization: Bearer <token>
- Headers:
Get Employee Report: GET
http://localhost:5000/api/admin/reports/employees
- Headers:
Authorization: Bearer <token>
- Headers:
Get Vehicle Report: GET
http://localhost:5000/api/admin/reports/vehicles
- Headers:
Authorization: Bearer <token>
- Headers:
Get Service Report: GET
http://localhost:5000/api/admin/reports/services
- Headers:
Authorization: Bearer <token>
- Headers:
Get Visit Report: GET
http://localhost:5000/api/admin/reports/visits
- Headers:
Authorization: Bearer <token>
- Headers:
In this module, we developed the admin area functionalities to enable administrators to view detailed reports on customers, employees, vehicles, services, and visits. We created routes, controllers, and services to encapsulate the business logic, ensuring a clean separation of concerns. In the next module, we will focus on building the customer management functionalities.
Module 4: Customer Management Module
In this module, we will implement the functionalities for managing customer details and contacts. This includes creating, reading, updating, and deleting customer information. We will also track customer interactions and maintain a history of their visits and services.
Step 1: Customer Routes
Create routes for customer management functionalities. Create a file named customerRoutes.js
in the routes
directory:
const express = require('express');
const router = express.Router();
const customerController = require('../controllers/customerController');
const authMiddleware = require('../middleware/authMiddleware');
// Define customer routes
router.post('/', authMiddleware.verifyAdmin, customerController.createCustomer);
router.get('/', authMiddleware.verifyToken, customerController.getAllCustomers);
router.get('/:id', authMiddleware.verifyToken, customerController.getCustomerById);
router.put('/:id', authMiddleware.verifyAdmin, customerController.updateCustomer);
router.delete('/:id', authMiddleware.verifyAdmin, customerController.deleteCustomer);
module.exports = router;
Step 2: Customer Controller
Create the customerController.js
in the controllers
directory to handle customer-specific requests:
const CustomerService = require('../services/customerService');
exports.createCustomer = async (req, res, next) => {
try {
const result = await CustomerService.createCustomer(req.body);
res.status(201).json(result);
} catch (error) {
next(error);
}
};
exports.getAllCustomers = async (req, res, next) => {
try {
const result = await CustomerService.getAllCustomers();
res.status(200).json(result);
} catch (error) {
next(error);
}
};
exports.getCustomerById = async (req, res, next) => {
try {
const result = await CustomerService.getCustomerById(req.params.id);
res.status(200).json(result);
} catch (error) {
next(error);
}
};
exports.updateCustomer = async (req, res, next) => {
try {
const result = await CustomerService.updateCustomer(req.params.id, req.body);
res.status(200).json(result);
} catch
Step 3: Customer Service Class
Create the customerService.js
in the services
directory to encapsulate the business logic for customer operations:
const Customer = require('../models/Customer');
class CustomerService {
static async createCustomer(data) {
const { firstName, lastName, email, phone, address } = data;
const customer = new Customer({
firstName,
lastName,
email,
phone,
address,
});
await customer.save();
return { message: 'Customer created successfully' };
}
static async getAllCustomers() {
const customers = await Customer.find();
return customers;
}
static async getCustomerById(id) {
const customer = await Customer.findById(id);
if (!customer) {
throw new Error('Customer not found');
}
return customer;
}
static async updateCustomer(id, data) {
const { firstName, lastName, email, phone, address } = data;
const customer = await Customer.findById(id);
if (!customer) {
throw new Error('Customer not found');
}
customer.firstName = firstName || customer.firstName;
customer.lastName = lastName || customer.lastName;
customer.email = email || customer.email;
customer.phone = phone || customer.phone;
customer.address = address || customer.address;
await customer.save();
return { message: 'Customer updated successfully' };
}
static async deleteCustomer(id) {
const customer = await Customer.findById(id);
if (!customer) {
throw new Error('Customer not found');
}
await customer.remove();
return { message: 'Customer deleted successfully' };
}
}
module.exports = CustomerService;
Step 3: Customer Service Class
Create the customerService.js
in the services
directory to encapsulate the business logic for customer operations:
const Customer = require('../models/Customer');
class CustomerService {
static async createCustomer(data) {
const { firstName, lastName, email, phone, address } = data;
const customer = new Customer({
firstName,
lastName,
email,
phone,
address,
});
await customer.save();
return { message: 'Customer created successfully' };
}
static async getAllCustomers() {
const customers = await Customer.find();
return customers;
}
static async getCustomerById(id) {
const customer = await Customer.findById(id);
if (!customer) {
throw new Error('Customer not found');
}
return customer;
}
static async updateCustomer(id, data) {
const { firstName, lastName, email, phone, address } = data;
const customer = await Customer.findById(id);
if (!customer) {
throw new Error('Customer not found');
}
customer.firstName = firstName || customer.firstName;
customer.lastName = lastName || customer.lastName;
customer.email = email || customer.email;
customer.phone = phone || customer.phone;
customer.address = address || customer.address;
await customer.save();
return { message: 'Customer updated successfully' };
}
static async deleteCustomer(id) {
const customer = await Customer.findById(id);
if (!customer) {
throw new Error('Customer not found');
}
await customer.remove();
return { message: 'Customer deleted successfully' };
}
}
module.exports = CustomerService;
Step 4: Customer Model
Ensure you have the Customer
model in the models
directory:
const mongoose = require('mongoose');
const customerSchema = new mongoose.Schema({
firstName: { type: String, required: true },
lastName: { type: String, required: true },
email: { type: String, required: true, unique: true },
phone: { type: String, required: true },
address: { type: String, required: true },
createdAt: { type: Date, default: Date.now },
});
module.exports = mongoose.model('Customer', customerSchema);
Step 5: Integrate Customer Routes
Ensure you include the customer routes in your main server file (server.js
):
// In your server.js
app.use('/api/customers', require('./src/routes/customerRoutes'));
Step 6: Testing the Implementation
Make sure to test the new functionalities:
- Create a new customer.
- Get all customers.
- Get a customer by ID.
- Update customer details.
- Delete a customer.
Testing with Postman or similar tool:
Create Customer: POST
http://localhost:5000/api/customers
{
"firstName": "John",
"lastName": "Doe",
"email": "john.doe@example.com",
"phone": "1234567890",
"address": "123 Main St"
}
Get All Customers: GET
http://localhost:5000/api/customers
- Headers:
Authorization: Bearer <token>
- Headers:
Get Customer by ID: GET
http://localhost:5000/api/customers/:id
- Headers:
Authorization: Bearer <token>
- Headers:
Update Customer: PUT
http://localhost:5000/api/customers/:id
{
"firstName": "Jane",
"lastName": "Doe",
"email": "jane.doe@example.com",
"phone": "0987654321",
"address": "456 Elm St"
}
Delete Customer: DELETE
http://localhost:5000/api/customers/:id
- Headers:
Authorization: Bearer <token>
- Headers:
In this module, we developed the customer management functionalities to enable creating, reading, updating, and deleting customer information. We created routes, controllers, and services to encapsulate the business logic, ensuring a clean separation of concerns. In the next module, we will focus on building the vehicle management functionalities.
Module 5: Vehicle Management Module
In this module, we will implement the functionalities for managing vehicle details. This includes creating, reading, updating, and deleting vehicle information. Each vehicle will be associated with a customer, and we will track the history of repairs and services performed on each vehicle.
Step 1: Vehicle Routes
Create routes for vehicle management functionalities. Create a file named vehicleRoutes.js
in the routes
directory:
const express = require('express');
const router = express.Router();
const vehicleController = require('../controllers/vehicleController');
const authMiddleware = require('../middleware/authMiddleware');
// Define vehicle routes
router.post('/', authMiddleware.verifyAdmin, vehicleController.createVehicle);
router.get('/', authMiddleware.verifyToken, vehicleController.getAllVehicles);
router.get('/:id', authMiddleware.verifyToken, vehicleController.getVehicleById);
router.put('/:id', authMiddleware.verifyAdmin, vehicleController.updateVehicle);
router.delete('/:id', authMiddleware.verifyAdmin, vehicleController.deleteVehicle);
module.exports = router;
Step 2: Vehicle Controller
Create the vehicleController.js
in the controllers
directory to handle vehicle-specific requests:
const VehicleService = require('../services/vehicleService');
exports.createVehicle = async (req, res, next) => {
try {
const result = await VehicleService.createVehicle(req.body);
res.status(201).json(result);
} catch (error) {
next(error);
}
};
exports.getAllVehicles = async (req, res, next) => {
try {
const result = await VehicleService.getAllVehicles();
res.status(200).json(result);
} catch (error) {
next(error);
}
};
exports.getVehicleById = async (req, res, next) => {
try {
const result = await VehicleService.getVehicleById(req.params.id);
res.status(200).json(result);
} catch (error) {
next(error);
}
};
exports.updateVehicle = async (req, res, next) => {
try {
const result = await VehicleService.updateVehicle(req.params.id, req.body);
res.status(200).json(result);
} catch (error) {
next(error);
}
};
exports.deleteVehicle = async (req, res, next) => {
try {
const result = await VehicleService.deleteVehicle(req.params.id);
res.status(200).json(result);
} catch (error) {
next(error);
}
};
Step 3: Vehicle Service Class
Create the vehicleService.js
in the services
directory to encapsulate the business logic for vehicle operations:
const Vehicle = require('../models/Vehicle');
const Customer = require('../models/Customer');
class VehicleService {
static async createVehicle(data) {
const { vin, licensePlate, customerId, make, model, year, details } = data;
const customer = await Customer.findById(customerId);
if (!customer) {
throw new Error('Customer not found');
}
const vehicle = new Vehicle({
vin,
licensePlate,
customer: customerId,
make,
model,
year,
details,
});
await vehicle.save();
return { message: 'Vehicle created successfully' };
}
static async getAllVehicles() {
const vehicles = await Vehicle.find().populate('customer');
return vehicles;
}
static async getVehicleById(id) {
const vehicle = await Vehicle.findById(id).populate('customer');
if (!vehicle) {
throw new Error('Vehicle not found');
}
return vehicle;
}
static async updateVehicle(id, data) {
const { vin, licensePlate, make, model, year, details } = data;
const vehicle = await Vehicle.findById(id);
if (!vehicle) {
throw new Error('Vehicle not found');
}
vehicle.vin = vin || vehicle.vin;
vehicle.licensePlate = licensePlate || vehicle.licensePlate;
vehicle.make = make || vehicle.make;
vehicle.model = model || vehicle.model;
vehicle.year = year || vehicle.year;
vehicle.details = details || vehicle.details;
await vehicle.save();
return { message: 'Vehicle updated successfully' };
}
static async deleteVehicle(id) {
const vehicle = await Vehicle.findById(id);
if (!vehicle) {
throw new Error('Vehicle not found');
}
await vehicle.remove();
return { message: 'Vehicle deleted successfully' };
}
}
module.exports = VehicleService;
Step 4: Vehicle Model
Ensure you have the Vehicle
model in the models
directory:
const mongoose = require('mongoose');
const vehicleSchema = new mongoose.Schema({
vin: { type: String, required: true, unique: true },
licensePlate: { type: String, required: true, unique: true },
customer: { type: mongoose.Schema.Types.ObjectId, ref: 'Customer', required: true },
make: { type: String, required: true },
model: { type: String, required: true },
year: { type: Number, required: true },
details: { type: String },
createdAt: { type: Date, default: Date.now },
});
module.exports = mongoose.model('Vehicle', vehicleSchema);
Step 5: Integrate Vehicle Routes
Ensure you include the vehicle routes in your main server file (server.js
):
// In your server.js
app.use('/api/vehicles', require('./src/routes/vehicleRoutes'));
Step 6: Testing the Implementation
Make sure to test the new functionalities:
- Create a new vehicle.
- Get all vehicles.
- Get a vehicle by ID.
- Update vehicle details.
- Delete a vehicle.
Testing with Postman or similar tool:
Create Vehicle: POST
http://localhost:5000/api/vehicles
{
"vin": "1HGCM82633A123456",
"licensePlate": "ABC1234",
"customerId": "60c72b2f9e1b8b001c8a4d33",
"make": "Honda",
"model": "Accord",
"year": 2020,
"details": "Silver sedan, low mileage"
}
Get All Vehicles: GET
http://localhost:5000/api/vehicles
- Headers:
Authorization: Bearer <token>
- Headers:
Get Vehicle by ID: GET
http://localhost:5000/api/vehicles/:id
- Headers:
Authorization: Bearer <token>
- Headers:
Update Vehicle: PUT
http://localhost:5000/api/vehicles/:id
{
"vin": "1HGCM82633A123456",
"licensePlate": "DEF5678",
"make": "Honda",
"model": "Accord",
"year": 2021,
"details": "Silver sedan, low mileage"
}
Delete Vehicle: DELETE
http://localhost:5000/api/vehicles/:id
- Headers:
Authorization: Bearer <token>
- Headers:
In this module, we developed the vehicle management functionalities to enable creating, reading, updating, and deleting vehicle information. We created routes, controllers, and services to encapsulate the business logic, ensuring a clean separation of concerns. In the next module, we will focus on building the service and offer management functionalities.
Module 6: Service and Offer Management Module
In this module, we will implement functionalities for managing services and offers. This includes creating, reading, updating, and deleting services and offers. Services will consist of individual tasks, and offers will be proposals given to customers, which can include multiple services or tasks.
Step 1: Service Routes
Create routes for service management functionalities. Create a file named serviceRoutes.js
in the routes
directory:
const express = require('express');
const router = express.Router();
const serviceController = require('../controllers/serviceController');
const authMiddleware = require('../middleware/authMiddleware');
// Define service routes
router.post('/', authMiddleware.verifyAdmin, serviceController.createService);
router.get('/', authMiddleware.verifyToken, serviceController.getAllServices);
router.get('/:id', authMiddleware.verifyToken, serviceController.getServiceById);
router.put('/:id', authMiddleware.verifyAdmin, serviceController.updateService);
router.delete('/:id', authMiddleware.verifyAdmin, serviceController.deleteService);
module.exports = router;
Step 2: Service Controller
Create the serviceController.js
in the controllers
directory to handle service-specific requests:
const ServiceService = require('../services/serviceService');
exports.createService = async (req, res, next) => {
try {
const result = await ServiceService.createService(req.body);
res.status(201).json(result);
} catch (error) {
next(error);
}
};
exports.getAllServices = async (req, res, next) => {
try {
const result = await ServiceService.getAllServices();
res.status(200).json(result);
} catch (error) {
next(error);
}
};
exports.getServiceById = async (req, res, next) => {
try {
const result = await ServiceService.getServiceById(req.params.id);
res.status(200).json(result);
} catch (error) {
next(error);
}
};
exports.updateService = async (req, res, next) => {
try {
const result = await ServiceService.updateService(req.params.id, req.body);
res.status(200).json(result);
} catch (error) {
next(error);
}
};
exports.deleteService = async (req, res, next) => {
try {
const result = await ServiceService.deleteService(req.params.id);
res.status(200).json(result);
} catch (error) {
next(error);
}
};
Step 3: Service Service Class
Create the serviceService.js
in the services
directory to encapsulate the business logic for service operations:
const Service = require('../models/Service');
class ServiceService {
static async createService(data) {
const { serviceName, description, price, isActive } = data;
const service = new Service({
serviceName,
description,
price,
isActive,
});
await service.save();
return { message: 'Service created successfully' };
}
static async getAllServices() {
const services = await Service.find();
return services;
}
static async getServiceById(id) {
const service = await Service.findById(id);
if (!service) {
throw new Error('Service not found');
}
return service;
}
static async updateService(id, data) {
const { serviceName, description, price, isActive } = data;
const service = await Service.findById(id);
if (!service) {
throw new Error('Service not found');
}
service.serviceName = serviceName || service.serviceName;
service.description = description || service.description;
service.price = price || service.price;
service.isActive = isActive !== undefined ? isActive : service.isActive;
await service.save();
return { message: 'Service updated successfully' };
}
static async deleteService(id) {
const service = await Service.findById(id);
if (!service) {
throw new Error('Service not found');
}
await service.remove();
return { message: 'Service deleted successfully' };
}
}
module.exports = ServiceService;
Step 4: Service Model
Ensure you have the Service
model in the models
directory:
const mongoose = require('mongoose');
const serviceSchema = new mongoose.Schema({
serviceName: { type: String, required: true },
description: { type: String },
price: { type: Number, required: true },
isActive: { type: Boolean, default: true },
createdAt: { type: Date, default: Date.now },
});
module.exports = mongoose.model('Service', serviceSchema);
Step 5: Offer Routes
Create routes for offer management functionalities. Create a file named offerRoutes.js
in the routes
directory:
const express = require('express');
const router = express.Router();
const offerController = require('../controllers/offerController');
const authMiddleware = require('../middleware/authMiddleware');
// Define offer routes
router.post('/', authMiddleware.verifyAdmin, offerController.createOffer);
router.get('/', authMiddleware.verifyToken, offerController.getAllOffers);
router.get('/:id', authMiddleware.verifyToken, offerController.getOfferById);
router.put('/:id', authMiddleware.verifyAdmin, offerController.updateOffer);
router.delete('/:id', authMiddleware.verifyAdmin, offerController.deleteOffer);
module.exports = router;
Step 6: Offer Controller
Create the offerController.js
in the controllers
directory to handle offer-specific requests:
const OfferService = require('../services/offerService');
exports.createOffer = async (req, res, next) => {
try {
const result = await OfferService.createOffer(req.body);
res.status(201).json(result);
} catch (error) {
next(error);
}
};
exports.getAllOffers = async (req, res, next) => {
try {
const result = await OfferService.getAllOffers();
res.status(200).json(result);
} catch (error) {
next(error);
}
};
exports.getOfferById = async (req, res, next) => {
try {
const result = await OfferService.getOfferById(req.params.id);
res.status(200).json(result);
} catch (error) {
next(error);
}
};
exports.updateOffer = async (req, res, next) => {
try {
const result = await OfferService.updateOffer(req.params.id, req.body);
res.status(200).json(result);
} catch (error) {
next(error);
}
};
exports.deleteOffer = async (req, res, next) => {
try {
const result = await OfferService.deleteOffer(req.params.id);
res.status(200).json(result);
} catch (error) {
next(error);
}
};
Step 7: Offer Service Class
Create the offerService.js
in the services
directory to encapsulate the business logic for offer operations:
Trading Analysis & Predictions
const Offer = require('../models/Offer');
const Customer = require('../models/Customer');
const Service = require('../models/Service');
class OfferService {
static async createOffer(data) {
const { customerId, serviceIds, offerDescription, offerPrice } = data;
const customer = await Customer.findById(customerId);
if (!customer) {
throw new Error('Customer not found');
}
const services = await Service.find({ _id: { $in: serviceIds } });
if (services.length !== serviceIds.length) {
throw new Error('Some services not found');
}
const offer = new Offer({
customer: customerId,
services: serviceIds,
offerDescription,
offerPrice,
});
await offer.save();
return { message: 'Offer created successfully' };
}
static async getAllOffers() {
const offers = await Offer.find().populate('customer services');
return offers;
}
static async getOfferById(id) {
const offer = await Offer.findById(id).populate('customer services');
if (!offer) {
throw new Error('Offer not found');
}
return offer;
}
static async updateOffer(id, data) {
const { customerId, serviceIds, offerDescription, offerPrice } = data;
const offer = await Offer.findById(id);
if (!offer) {
throw new Error('Offer not found');
}
if (customerId) {
const customer = await Customer.findById(customerId);
if (!customer) {
throw new Error('Customer not found');
}
offer.customer = customerId;
}
if (serviceIds) {
const services = await Service.find({ _id: { $in: serviceIds } });
if (services.length !== serviceIds.length) {
throw new Error('Some services not found');
}
offer.services = serviceIds;
}
offer.offerDescription = offerDescription || offer.offerDescription;
offer.offerPrice = offerPrice || offer.offerPrice;
await offer.save();
return { message: 'Offer updated successfully' };
}
static async deleteOffer(id) {
const offer = await Offer.findById(id);
if (!offer) {
throw new Error('Offer not found');
}
await offer.remove();
return { message: 'Offer deleted successfully' };
}
}
module.exports = OfferService;
Step 8: Offer Model
Ensure you have the Offer
model in the models
directory:
const mongoose = require('mongoose');
const offerSchema = new mongoose.Schema({
customer: { type: mongoose.Schema.Types.ObjectId, ref: 'Customer', required: true },
services: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Service', required: true }],
offerDescription: { type: String },
offerPrice: { type: Number, required: true },
createdAt: { type: Date, default: Date.now },
});
module.exports = mongoose.model('Offer', offerSchema);
Step 9: Integrate Service and Offer Routes
Ensure you include the service and offer routes in your main server file (server.js
):
// In your server.js
app.use('/api/services', require('./src/routes/serviceRoutes'));
app.use('/api/offers', require('./src/routes/offerRoutes'));
Step 10: Testing the Implementation
Make sure to test the new functionalities:
- Create a new service.
- Get all services.
- Get a service by ID.
- Update service details.
- Delete a service.
- Create a new offer.
- Get all offers.
- Get an offer by ID.
- Update offer details.
- Delete an offer.
Testing with Postman or similar tool:
Create Service: POST
http://localhost:5000/api/services
{
"serviceName": "Oil Change",
"description": "Full synthetic oil change",
"price": 50,
"isActive": true
}
Get All Services: GET
http://localhost:5000/api/services
- Headers:
Authorization: Bearer <token>
- Headers:
Get Service by ID: GET
http://localhost:5000/api/services/:id
- Headers:
Authorization: Bearer <token>
- Headers:
Update Service: PUT
http://localhost:5000/api/services/:id
{
"serviceName": "Premium Oil Change",
"description": "Premium full synthetic oil change",
"price": 75,
"isActive": true
}
Delete Service: DELETE
http://localhost:5000/api/services/:id
- Headers:
Authorization: Bearer <token>
- Headers:
Create Offer: POST
http://localhost:5000/api/offers
{
"customerId": "60c72b2f9e1b8b001c8a4d33",
"serviceIds": ["60c72b2f9e1b8b001c8a4d34", "60c72b2f9e1b8b001c8a4d35"],
"offerDescription": "Summer maintenance package",
"offerPrice": 200
}
Get All Offers: GET
http://localhost:5000/api/offers
- Headers:
Authorization: Bearer <token>
- Headers:
Get Offer by ID: GET
http://localhost:5000/api/offers/:id
- Headers:
Authorization: Bearer <token>
- Headers:
Update Offer: PUT
http://localhost:5000/api/offers/:id
{
"customerId": "60c72b2f9e1b8b001c8a4d33",
"serviceIds": ["60c72b2f9e1b8b001c8a4d34"],
"offerDescription": "Updated maintenance package",
"offerPrice": 180
}
Delete Offer: DELETE
http://localhost:5000/api/offers/:id
- Headers:
Authorization: Bearer <token>
- Headers:
In this module, we developed the service and offer management functionalities to enable creating, reading, updating, and deleting services and offers. We created routes, controllers, and services to encapsulate the business logic, ensuring a clean separation of concerns. In the next module, we will focus on building the visit management functionalities.
Module 7: Visit Management Module
In this module, we will implement the functionalities for managing customer visits. This includes creating, reading, updating, and deleting visit records. Each visit will be associated with a customer, a vehicle, and one or more services or tasks performed during the visit.
Step 1: Visit Routes
Create routes for visit management functionalities. Create a file named visitRoutes.js
in the routes
directory:
const express = require('express');
const router = express.Router();
const visitController = require('../controllers/visitController');
const authMiddleware = require('../middleware/authMiddleware');
// Define visit routes
router.post('/', authMiddleware.verifyAdmin, visitController.createVisit);
router.get('/', authMiddleware.verifyToken, visitController.getAllVisits);
router.get('/:id', authMiddleware.verifyToken, visitController.getVisitById);
router.put('/:id', authMiddleware.verifyAdmin, visitController.updateVisit);
router.delete('/:id', authMiddleware.verifyAdmin, visitController.deleteVisit);
module.exports = router;
Step 2: Visit Controller
Create the visitController.js
in the controllers
directory to handle visit-specific requests:
const VisitService = require('../services/visitService');
exports.createVisit = async (req, res, next) => {
try {
const result = await VisitService.createVisit(req.body);
res.status(201).json(result);
} catch (error) {
next(error);
}
};
exports.getAllVisits = async (req, res, next) => {
try {
const result = await VisitService.getAllVisits();
res.status(200).json(result);
} catch (error) {
next(error);
}
};
exports.getVisitById = async (req, res, next) => {
try {
const result = await VisitService.getVisitById(req.params.id);
res.status(200).json(result);
} catch (error) {
next(error);
}
};
exports.updateVisit = async (req, res, next) => {
try {
const result = await VisitService.updateVisit(req.params.id, req.body);
res.status(200).json(result);
} catch (error) {
next(error);
}
};
exports.deleteVisit = async (req, res, next) => {
try {
const result = await VisitService.deleteVisit(req.params.id);
res.status(200).json(result);
} catch (error) {
next(error);
}
};
Step 3: Visit Service Class
Create the visitService.js
in the services
directory to encapsulate the business logic for visit operations:
const Visit = require('../models/Visit');
const Customer = require('../models/Customer');
const Vehicle = require('../models/Vehicle');
const Service = require('../models/Service');
class VisitService {
static async createVisit(data) {
const { repairShopId, customerId, vehicleId, services, visitStartDate, visitEndDate, status, totalPrice } = data;
const customer = await Customer.findById(customerId);
if (!customer) {
throw new Error('Customer not found');
}
const vehicle = await Vehicle.findById(vehicleId);
if (!vehicle) {
throw new Error('Vehicle not found');
}
const serviceRecords = await Service.find({ _id: { $in: services } });
if (serviceRecords.length !== services.length) {
throw new Error('Some services not found');
}
const visit = new Visit({
repairShop: repairShopId,
customer: customerId,
vehicle: vehicleId,
services: services,
visitStartDate: visitStartDate,
visitEndDate: visitEndDate,
status: status,
totalPrice: totalPrice,
});
await visit.save();
return { message: 'Visit created successfully' };
}
static async getAllVisits() {
const visits = await Visit.find().populate('customer vehicle services');
return visits;
}
static async getVisitById(id) {
const visit = await Visit.findById(id).populate('customer vehicle services');
if (!visit) {
throw new Error('Visit not found');
}
return visit;
}
static async updateVisit(id, data) {
const { repairShopId, customerId, vehicleId, services, visitStartDate, visitEndDate, status, totalPrice } = data;
const visit = await Visit.findById(id);
if (!visit) {
throw new Error('Visit not found');
}
if (repairShopId) visit.repairShop = repairShopId;
if (customerId) {
const customer = await Customer.findById(customerId);
if (!customer) {
throw new Error('Customer not found');
}
visit.customer = customerId;
}
if (vehicleId) {
const vehicle = await Vehicle.findById(vehicleId);
if (!vehicle) {
throw new Error('Vehicle not found');
}
visit.vehicle = vehicleId;
}
if (services) {
const serviceRecords = await Service.find({ _id: { $in: services } });
if (serviceRecords.length !== services.length) {
throw new Error('Some services not found');
}
visit.services = services;
}
if (visitStartDate) visit.visitStartDate = visitStartDate;
if (visitEndDate) visit.visitEndDate = visitEndDate;
if (status) visit.status = status;
if (totalPrice) visit.totalPrice = totalPrice;
await visit.save();
return { message: 'Visit updated successfully' };
}
static async deleteVisit(id) {
const visit = await Visit.findById(id);
if (!visit) {
throw new Error('Visit not found');
}
await visit.remove();
return { message: 'Visit deleted successfully' };
}
}
module.exports = VisitService;
Step 4: Visit Model
Ensure you have the Visit
model in the models
directory:
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: 'Customer', required: true },
vehicle: { type: mongoose.Schema.Types.ObjectId, ref: 'Vehicle', required: true },
services: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Service', required: true }],
visitStartDate: { type: Date, required: true },
visitEndDate: { type: Date },
status: { type: String, required: true },
totalPrice: { type: Number, required: true },
createdAt: { type: Date, default: Date.now },
});
module.exports = mongoose.model('Visit', visitSchema);
Step 5: Integrate Visit Routes
Ensure you include the visit routes in your main server file (server.js
):
// In your server.js
app.use('/api/visits', require('./src/routes/visitRoutes'));
Step 6: Testing the Implementation
Make sure to test the new functionalities:
- Create a new visit.
- Get all visits.
- Get a visit by ID.
- Update visit details.
- Delete a visit.
Testing with Postman or similar tool:
Create Visit: POST
http://localhost:5000/api/visits
{
"repairShopId": "60c72b2f9e1b8b001c8a4d36",
"customerId": "60c72b2f9e1b8b001c8a4d33",
"vehicleId": "60c72b2f9e1b8b001c8a4d34",
"services": ["60c72b2f9e1b8b001c8a4d35", "60c72b2f9e1b8b001c8a4d37"],
"visitStartDate": "2021-06-13T10:00:00.000Z",
"visitEndDate": "2021-06-13T15:00:00.000Z",
"status": "Completed",
"totalPrice": 300
}
Get All Visits: GET
http://localhost:5000/api/visits
- Headers:
Authorization: Bearer <token>
- Headers:
Get Visit by ID: GET
http://localhost:5000/api/visits/:id
- Headers:
Authorization: Bearer <token>
- Headers:
Update Visit: PUT
http://localhost:5000/api/visits/:id
{
"status": "In Progress",
"visitEndDate": "2021-06-13T17:00:00.000Z",
"totalPrice": 350
}
Delete Visit: DELETE
http://localhost:5000/api/visits/:id
- Headers:
Authorization: Bearer <token>
- Headers:
In this module, we developed the visit management functionalities to enable creating, reading, updating, and deleting visit records. We created routes, controllers, and services to encapsulate the business logic, ensuring a clean separation of concerns. In the next module, we will focus on building the notification system functionalities.
Module 8: Amazing
In this module, we will implement a notification system to send alerts to customers and employees. Notifications will be sent via SMS and email using Twilio and Nodemailer, respectively. Key events such as the start and completion of repairs will trigger these notifications.
Step 1: Install Dependencies
First, we need to install the necessary dependencies for sending SMS and email notifications.
npm install nodemailer twilio
Step 2: Environment Configuration
Update the .env
file to include the necessary configuration for Twilio and Nodemailer:
TWILIO_ACCOUNT_SID=your_twilio_account_sid
TWILIO_AUTH_TOKEN=your_twilio_auth_token
TWILIO_PHONE_NUMBER=your_twilio_phone_number
EMAIL_HOST=smtp.your-email-provider.com
EMAIL_PORT=587
EMAIL_USER=your_email@example.com
EMAIL_PASS=your_email_password
Step 3: Notification Service
Create the notificationService.js
in the services
directory to encapsulate the business logic for sending notifications:
const nodemailer = require('nodemailer');
const twilio = require('twilio');
class NotificationService {
constructor() {
this.twilioClient = twilio(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN);
this.emailTransporter = nodemailer.createTransport({
host: process.env.EMAIL_HOST,
port: process.env.EMAIL_PORT,
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASS,
},
});
}
async sendEmail(to, subject, text) {
const mailOptions = {
from: process.env.EMAIL_USER,
to,
subject,
text,
};
await this.emailTransporter.sendMail(mailOptions);
}
async sendSMS(to, message) {
await this.twilioClient.messages.create({
body: message,
from: process.env.TWILIO_PHONE_NUMBER,
to,
});
}
async notifyRepairStarted(customerEmail, customerPhone, repairDetails) {
const emailSubject = 'Repair Started';
const emailText = `Dear Customer, your vehicle repair has started. Details: ${repairDetails}`;
const smsText = `Repair Started: ${repairDetails}`;
if (customerEmail) {
await this.sendEmail(customerEmail, emailSubject, emailText);
}
if (customerPhone) {
await this.sendSMS(customerPhone, smsText);
}
}
async notifyRepairCompleted(customerEmail, customerPhone, repairDetails) {
const emailSubject = 'Repair Completed';
const emailText = `Dear Customer, your vehicle repair is complete. Details: ${repairDetails}`;
const smsText = `Repair Completed: ${repairDetails}`;
if (customerEmail) {
await this.sendEmail(customerEmail, emailSubject, emailText);
}
if (customerPhone) {
await this.sendSMS(customerPhone, smsText);
}
}
}
module.exports = new NotificationService();
Step 4: Integrate Notification Service in Visit Service
Update the visitService.js
to include notification triggers when a repair starts or completes:
const Visit = require('../models/Visit');
const Customer = require('../models/Customer');
const Vehicle = require('../models/Vehicle');
const Service = require('../models/Service');
const NotificationService = require('./notificationService');
class VisitService {
static async createVisit(data) {
const { repairShopId, customerId, vehicleId, services, visitStartDate, visitEndDate, status, totalPrice } = data;
const customer = await Customer.findById(customerId);
if (!customer) {
throw new Error('Customer not found');
}
const vehicle = await Vehicle.findById(vehicleId);
if (!vehicle) {
throw new Error('Vehicle not found');
}
const serviceRecords = await Service.find({ _id: { $in: services } });
if (serviceRecords.length !== services.length) {
throw new Error('Some services not found');
}
const visit = new Visit({
repairShop: repairShopId,
customer: customerId,
vehicle: vehicleId,
services: services,
visitStartDate: visitStartDate,
visitEndDate: visitEndDate,
status: status,
totalPrice: totalPrice,
});
await visit.save();
if (status === 'Started') {
await NotificationService.notifyRepairStarted(customer.email, customer.phone, `Visit ID: ${visit._id}`);
}
return { message: 'Visit created successfully' };
}
static async updateVisit(id, data) {
const { repairShopId, customerId, vehicleId, services, visitStartDate, visitEndDate, status, totalPrice } = data;
const visit = await Visit.findById(id);
if (!visit) {
throw new Error('Visit not found');
}
if (repairShopId) visit.repairShop = repairShopId;
if (customerId) {
const customer = await Customer.findById(customerId);
if (!customer) {
throw new Error('Customer not found');
}
visit.customer = customerId;
}
if (vehicleId) {
const vehicle = await Vehicle.findById(vehicleId);
if (!vehicle) {
throw new Error('Vehicle not found');
}
visit.vehicle = vehicleId;
}
if (services) {
const serviceRecords = await Service.find({ _id: { $in: services } });
if (serviceRecords.length !== services.length) {
throw new Error('Some services not found');
}
visit.services = services;
}
if (visitStartDate) visit.visitStartDate = visitStartDate;
if (visitEndDate) visit.visitEndDate = visitEndDate;
if (status) visit.status = status;
if (totalPrice) visit.totalPrice = totalPrice;
await visit.save();
if (status === 'Completed') {
const customer = await Customer.findById(visit.customer);
await NotificationService.notifyRepairCompleted(customer.email, customer.phone, `Visit ID: ${visit._id}`);
}
return { message: 'Visit updated successfully' };
}
static async getAllVisits() {
const visits = await Visit.find().populate('customer vehicle services');
return visits;
}
static async getVisitById(id) {
const visit = await Visit.findById(id).populate('customer vehicle services');
if (!visit) {
throw new Error('Visit not found');
}
return visit;
}
static async deleteVisit(id) {
const visit = await Visit.findById(id);
if (!visit) {
throw new Error('Visit not found');
}
await visit.remove();
return { message: 'Visit deleted successfully' };
}
}
module.exports = VisitService;
Step 5: Testing the Implementation
Make sure to test the new functionalities:
- Create a new visit and check if the “Repair Started” notification is sent.
- Update a visit status to “Completed” and check if the “Repair Completed” notification is sent.
Testing with Postman or similar tool:
Create Visit: POST
http://localhost:5000/api/visits
{
"repairShopId": "60c72b2f9e1b8b001c8a4d36",
"customerId": "60c72b2f9e1b8b001c8a4d33",
"vehicleId": "60c72b2f9e1b8b001c8a4d34",
"services": ["60c72b2f9e1b8b001c8a4d35", "60c72b2f9e1b8b001c8a4d37"],
"visitStartDate": "2021-06-13T10:00:00.000Z",
"visitEndDate": "2021-06-13T15:00:00.000Z",
"status": "Started",
"totalPrice": 300
}
Update Visit: PUT http://localhost:5000/api/visits/:id
{
"status": "Completed",
"visitEndDate": "2021-06-13T17:00:00.000Z",
"totalPrice": 350
}
In this module, we developed the notification system functionalities to send alerts via SMS and email when key events occur, such as the start and completion of repairs. We created a notification service using Twilio and Nodemailer, and integrated it with the visit management functionalities. In the next module, we will focus on building the billing system functionalities.
Module 9: Billing System Module
In this module, we will implement a billing system to handle payment processing using Stripe. This includes generating invoices for completed services, processing payments, and tracking payment statuses.
Step 1: Install Stripe Dependency
First, we need to install the Stripe dependency:
npm install stripe
Step 2: Environment Configuration
Update the .env
file to include the Stripe configuration:
STRIPE_SECRET_KEY=your_stripe_secret_key
Step 3: Billing Service
Create the billingService.js
in the services
directory to encapsulate the business logic for billing and payment processing:
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const Visit = require('../models/Visit');
const Invoice = require('../models/Invoice');
class BillingService {
static async createInvoice(data) {
const { visitId, amount, currency } = data;
const visit = await Visit.findById(visitId).populate('customer vehicle services');
if (!visit) {
throw new Error('Visit not found');
}
const invoice = new Invoice({
visit: visitId,
customer: visit.customer._id,
amount,
currency,
status: 'pending',
});
await invoice.save();
return { message: 'Invoice created successfully', invoice };
}
static async processPayment(invoiceId, token) {
const invoice = await Invoice.findById(invoiceId);
if (!invoice) {
throw new Error('Invoice not found');
}
if (invoice.status !== 'pending') {
throw new Error('Invoice is not pending');
}
const charge = await stripe.charges.create({
amount: invoice.amount,
currency: invoice.currency,
source: token,
description: `Payment for invoice ${invoice._id}`,
});
invoice.status = 'paid';
invoice.paymentDate = new Date();
await invoice.save();
return { message: 'Payment processed successfully', charge };
}
static async getAllInvoices() {
const invoices = await Invoice.find().populate('visit customer');
return invoices;
}
static async getInvoiceById(id) {
const invoice = await Invoice.findById(id).populate('visit customer');
if (!invoice) {
throw new Error('Invoice not found');
}
return invoice;
}
}
module.exports = BillingService;
Step 4: Invoice Model
Create the Invoice
model in the models
directory:
const mongoose = require('mongoose');
const invoiceSchema = new mongoose.Schema({
visit: { type: mongoose.Schema.Types.ObjectId, ref: 'Visit', required: true },
customer: { type: mongoose.Schema.Types.ObjectId, ref: 'Customer', required: true },
amount: { type: Number, required: true },
currency: { type: String, required: true },
status: { type: String, enum: ['pending', 'paid', 'failed'], default: 'pending' },
paymentDate: { type: Date },
createdAt: { type: Date, default: Date.now },
});
module.exports = mongoose.model('Invoice', invoiceSchema);
Step 5: Billing Controller
Create the billingController.js
in the controllers
directory to handle billing-specific requests:
const BillingService = require('../services/billingService');
exports.createInvoice = async (req, res, next) => {
try {
const result = await BillingService.createInvoice(req.body);
res.status(201).json(result);
} catch (error) {
next(error);
}
};
exports.processPayment = async (req, res, next) => {
try {
const { invoiceId, token } = req.body;
const result = await BillingService.processPayment(invoiceId, token);
res.status(200).json(result);
} catch (error) {
next(error);
}
};
exports.getAllInvoices = async (req, res, next) => {
try {
const result = await BillingService.getAllInvoices();
res.status(200).json(result);
} catch (error) {
next(error);
}
};
exports.getInvoiceById = async (req, res, next) => {
try {
const result = await BillingService.getInvoiceById(req.params.id);
res.status(200).json(result);
} catch (error) {
next(error);
}
};
Step 6: Billing Routes
Create routes for billing management functionalities. Create a file named billingRoutes.js
in the routes
directory:
const express = require('express');
const router = express.Router();
const billingController = require('../controllers/billingController');
const authMiddleware = require('../middleware/authMiddleware');
// Define billing routes
router.post('/invoices', authMiddleware.verifyAdmin, billingController.createInvoice);
router.post('/payment', authMiddleware.verifyToken, billingController.processPayment);
router.get('/invoices', authMiddleware.verifyAdmin, billingController.getAllInvoices);
router.get('/invoices/:id', authMiddleware.verifyAdmin, billingController.getInvoiceById);
module.exports = router;
Step 7: Integrate Billing Routes
Ensure you include the billing routes in your main server file (server.js
):
// In your server.js
app.use('/api/billing', require('./src/routes/billingRoutes'));
Step 8: Testing the Implementation
Make sure to test the new functionalities:
- Create a new invoice.
- Process a payment.
- Get all invoices.
- Get an invoice by ID.
Testing with Postman or similar tool:
Create Invoice: POST
http://localhost:5000/api/billing/invoices
{
"visitId": "60c72b2f9e1b8b001c8a4d39",
"amount": 30000,
"currency": "usd"
}
Process Payment: POST http://localhost:5000/api/billing/payment
{
"invoiceId": "60c72b2f9e1b8b001c8a4d3a",
"token": "tok_visa"
}
Get All Invoices: GET
http://localhost:5000/api/billing/invoices
- Headers:
Authorization: Bearer <token>
- Headers:
Get Invoice by ID: GET
http://localhost:5000/api/billing/invoices/:id
- Headers:
Authorization: Bearer <token>
- Headers:
In this module, we developed the billing system functionalities to handle payment processing using Stripe. We created a billing service to encapsulate the business logic for generating invoices and processing payments, and integrated these functionalities with the visit management module. In the next module, we will focus on implementing pagination, filtering, and search functionalities.
Module 10: Pagination, Filtering, and Search Module
In this module, we will implement pagination, filtering, and search functionalities to handle large datasets efficiently. These functionalities will be applied to customers, vehicles, services, and visits.
Step 1: Pagination, Filtering, and Search Middleware
Create a middleware to handle pagination, filtering, and search. Create a file named paginationMiddleware.js
in the middleware
directory:
const applyPagination = (query, req) => {
const page = parseInt(req.query.page, 10) || 1;
const limit = parseInt(req.query.limit, 10) || 10;
const skip = (page - 1) * limit;
query.skip(skip).limit(limit);
};
const applyFiltering = (query, req) => {
const filters = { ...req.query };
const excludeFields = ['page', 'limit', 'sort', 'fields', 'search'];
excludeFields.forEach((el) => delete filters[el]);
let queryStr = JSON.stringify(filters);
queryStr = queryStr.replace(/\b(gte|gt|lte|lt|in)\b/g, (match) => `$${match}`);
query.find(JSON.parse(queryStr));
};
const applySorting = (query, req) => {
if (req.query.sort) {
const sortBy = req.query.sort.split(',').join(' ');
query.sort(sortBy);
} else {
query.sort('-createdAt');
}
};
const applyFieldLimiting = (query, req) => {
if (req.query.fields) {
const fields = req.query.fields.split(',').join(' ');
query.select(fields);
} else {
query.select('-__v');
}
};
const applySearch = (query, req) => {
if (req.query.search) {
const search = req.query.search;
query.find({ $text: { $search: search } });
}
};
const paginationMiddleware = (Model) => async (req, res, next) => {
try {
let query = Model.find();
applyFiltering(query, req);
applySearch(query, req);
applySorting(query, req);
applyFieldLimiting(query, req);
applyPagination(query, req);
const results = await query;
const total = await Model.countDocuments(query.getQuery());
res.paginationResults = {
success: true,
count: results.length,
total,
data: results,
};
next();
} catch (error) {
next(error);
}
};
module.exports = paginationMiddleware;
Step 2: Update Routes to Include Pagination Middleware
Update the routes for customers, vehicles, services, and visits to include the pagination middleware.
Customer Routes
Update customerRoutes.js
:
const express = require('express');
const router = express.Router();
const customerController = require('../controllers/customerController');
const authMiddleware = require('../middleware/authMiddleware');
const paginationMiddleware = require('../middleware/paginationMiddleware');
const Customer = require('../models/Customer');
// Define customer routes
router.post('/', authMiddleware.verifyAdmin, customerController.createCustomer);
router.get('/', authMiddleware.verifyToken, paginationMiddleware(Customer), customerController.getAllCustomers);
router.get('/:id', authMiddleware.verifyToken, customerController.getCustomerById);
router.put('/:id', authMiddleware.verifyAdmin, customerController.updateCustomer);
router.delete('/:id', authMiddleware.verifyAdmin, customerController.deleteCustomer);
module.exports = router;
Vehicle Routes
Update vehicleRoutes.js
:
const express = require('express');
const router = express.Router();
const vehicleController = require('../controllers/vehicleController');
const authMiddleware = require('../middleware/authMiddleware');
const paginationMiddleware = require('../middleware/paginationMiddleware');
const Vehicle = require('../models/Vehicle');
// Define vehicle routes
router.post('/', authMiddleware.verifyAdmin, vehicleController.createVehicle);
router.get('/', authMiddleware.verifyToken, paginationMiddleware(Vehicle), vehicleController.getAllVehicles);
router.get('/:id', authMiddleware.verifyToken, vehicleController.getVehicleById);
router.put('/:id', authMiddleware.verifyAdmin, vehicleController.updateVehicle);
router.delete('/:id', authMiddleware.verifyAdmin, vehicleController.deleteVehicle);
module.exports = router;
Service Routes
Update serviceRoutes.js
:
const express = require('express');
const router = express.Router();
const serviceController = require('../controllers/serviceController');
const authMiddleware = require('../middleware/authMiddleware');
const paginationMiddleware = require('../middleware/paginationMiddleware');
const Service = require('../models/Service');
// Define service routes
router.post('/', authMiddleware.verifyAdmin, serviceController.createService);
router.get('/', authMiddleware.verifyToken, paginationMiddleware(Service), serviceController.getAllServices);
router.get('/:id', authMiddleware.verifyToken, serviceController.getServiceById);
router.put('/:id', authMiddleware.verifyAdmin, serviceController.updateService);
router.delete('/:id', authMiddleware.verifyAdmin, serviceController.deleteService);
module.exports = router;
Visit Routes
Update visitRoutes.js
:
const express = require('express');
const router = express.Router();
const visitController = require('../controllers/visitController');
const authMiddleware = require('../middleware/authMiddleware');
const paginationMiddleware = require('../middleware/paginationMiddleware');
const Visit = require('../models/Visit');
// Define visit routes
router.post('/', authMiddleware.verifyAdmin, visitController.createVisit);
router.get('/', authMiddleware.verifyToken, paginationMiddleware(Visit), visitController.getAllVisits);
router.get('/:id', authMiddleware.verifyToken, visitController.getVisitById);
router.put('/:id', authMiddleware.verifyAdmin, visitController.updateVisit);
router.delete('/:id', authMiddleware.verifyAdmin, visitController.deleteVisit);
module.exports = router;
Step 3: Update Controllers to Use Pagination Results
Update the controllers to use the pagination results provided by the middleware.
Customer Controller
Update customerController.js
:
const CustomerService = require('../services/customerService');
exports.createCustomer = async (req, res, next) => {
try {
const result = await CustomerService.createCustomer(req.body);
res.status(201).json(result);
} catch (error) {
next(error);
}
};
exports.getAllCustomers = async (req, res, next) => {
try {
res.status(200).json(res.paginationResults);
} catch (error) {
next(error);
}
};
exports.getCustomerById = async (req, res, next) => {
try {
const result = await CustomerService.getCustomerById(req.params.id);
res.status(200).json(result);
} catch (error) {
next(error);
}
};
exports.updateCustomer = async (req, res, next) => {
try {
const result = await CustomerService.updateCustomer(req.params.id, req.body);
res.status(200).json(result);
} catch (error) {
next(error);
}
};
exports.deleteCustomer = async (req, res, next) => {
try {
const result = await CustomerService.deleteCustomer(req.params.id);
res.status(200).json(result);
} catch (error) {
next(error);
}
};
Vehicle Controller
Update vehicleController.js
:
const VehicleService = require('../services/vehicleService');
exports.createVehicle = async (req, res, next) => {
try {
const result = await VehicleService.createVehicle(req.body);
res.status(201).json(result);
} catch (error) {
next(error);
}
};
exports.getAllVehicles = async (req, res, next) => {
try {
res.status(200).json(res.paginationResults);
} catch (error) {
next(error);
}
};
exports.getVehicleById = async (req, res, next) => {
try {
const result = await VehicleService.getVehicleById(req.params.id);
res.status(200).json(result);
} catch (error) {
next(error);
}
};
exports.updateVehicle = async (req, res, next) => {
try {
const result = await VehicleService.updateVehicle(req.params.id, req.body);
res.status(200).json(result);
} catch (error) {
next(error);
}
};
exports.deleteVehicle = async (req, res, next) => {
try {
const result = await VehicleService.deleteVehicle(req.params.id);
res.status(200).json(result);
} catch (error) {
next(error);
}
};
Service Controller
Update serviceController.js
:
const ServiceService = require('../services/serviceService');
exports.createService = async (req, res, next) => {
try {
const result = await ServiceService.createService(req.body);
res.status(201).json(result);
} catch (error) {
next(error);
}
};
exports.getAllServices = async (req, res, next) => {
try {
res.status(200).json(res.paginationResults);
} catch (error) {
next(error);
}
};
exports.getServiceById = async (req, res, next) => {
try {
const result = await ServiceService.getServiceById(req.params.id);
res.status(200).json(result);
} catch (error) {
next(error);
}
};
exports.updateService = async (req, res, next) => {
try {
const result = await ServiceService.updateService(req.params.id, req.body);
res.status(200).json(result);
} catch (error) {
next(error);
}
};
exports.deleteService = async (req, res, next) => {
try {
const result = await ServiceService.deleteService(req.params.id);
res.status(200).json(result);
} catch (error) {
next(error);
}
};
Visit Controller
Update visitController.js
:
const VisitService = require('../services/visitService');
exports.createVisit = async (req, res, next) => {
try {
const result = await VisitService.createVisit(req.body);
res.status(201).json(result);
} catch (error) {
next(error);
}
};
exports.getAllVisits = async (req, res, next) => {
try {
res.status(200).json(res.paginationResults);
} catch (error) {
next(error);
}
};
exports.getVisitById = async (req, res, next) => {
try {
const result = await VisitService.getVisitById(req.params.id);
res.status(200).json(result);
} catch (error) {
next(error);
}
};
exports.updateVisit = async (req, res, next) => {
try {
const result = await VisitService.updateVisit(req.params.id, req.body);
res.status(200).json(result);
} catch (error) {
next(error);
}
};
exports.deleteVisit = async (req, res, next) => {
try {
const result = await VisitService.deleteVisit(req.params.id);
res.status(200).json(result);
} catch (error) {
next(error);
}
};
Step 4: Testing the Implementation
Make sure to test the new functionalities with pagination, filtering, and search parameters:
- Get all customers with pagination, filtering, and search.
- Get all vehicles with pagination, filtering, and search.
- Get all services with pagination, filtering, and search.
- Get all visits with pagination, filtering, and search.
Testing with Postman or similar tool:
Get All Customers: GET
http://localhost:5000/api/customers
- Headers:
Authorization: Bearer <token>
- Query Parameters:
?page=1&limit=10&sort=firstName&fields=firstName,lastName,email&search=John
- Headers:
Get All Vehicles: GET
http://localhost:5000/api/vehicles
- Headers:
Authorization: Bearer <token>
- Query Parameters:
?page=1&limit=10&sort=make&fields=make,model,licensePlate&search=Honda
- Headers:
Get All Services: GET
http://localhost:5000/api/services
- Headers:
Authorization: Bearer <token>
- Query Parameters:
?page=1&limit=10&sort=serviceName&fields=serviceName,price&search=Oil Change
- Headers:
Get All Visits: GET
http://localhost:5000/api/visits
- Headers:
Authorization: Bearer <token>
- Query Parameters:
?page=1&limit=10&sort=visitStartDate&fields=visitStartDate,visitEndDate,status&search=Completed
- Headers:
In this module, we developed the functionalities for pagination, filtering, and search to handle large datasets efficiently. We created a middleware to encapsulate the logic for these functionalities and integrated it with the existing routes and controllers for customers, vehicles, services, and visits. In the next module, we will focus on deployment and testing of the entire application.
Module 11: Deployment and Testing
In this final module, we will focus on deploying the application and setting up a testing environment. This includes configuring the deployment to a cloud platform like Heroku, setting up environment variables, and writing unit and integration tests to ensure the application works as expected.
Step 1: Prepare for Deployment
Create a Procfile
Create a Procfile
in the root directory of your project to define the command that Heroku should run to start your app:
web: node server.js
Update package.json
Ensure your package.json
includes a start
script:
"scripts": {
"start": "node server.js"
}
Environment Variables
Ensure all environment variables are properly set up in your .env
file. When deploying to Heroku, you’ll need to configure these variables in the Heroku dashboard.
Heroku Deployment
Login to Heroku:
heroku login
Create a New Heroku App:
heroku create your-app-name
Add MongoDB Add-on:
heroku addons:create mongolab
Deploy the Application:
git push heroku main
Set Environment Variables:
heroku config:set JWT_SECRET=your_jwt_secret_key
heroku config:set CLOUDINARY_CLOUD_NAME=your_cloudinary_cloud_name
heroku config:set CLOUDINARY_API_KEY=your_cloudinary_api_key
heroku config:set CLOUDINARY_API_SECRET=your_cloudinary_api_secret
heroku config:set STRIPE_SECRET_KEY=your_stripe_secret_key
heroku config:set EMAIL_HOST=smtp.your-email-provider.com
heroku config:set EMAIL_PORT=587
heroku config:set EMAIL_USER=your_email@example.com
heroku config:set EMAIL_PASS=your_email_password
heroku config:set TWILIO_ACCOUNT_SID=your_twilio_account_sid
heroku config:set TWILIO_AUTH_TOKEN=your_twilio_auth_token
heroku config:set TWILIO_PHONE_NUMBER=your_twilio_phone_number
Step 2: Set Up Testing Environment
Install Testing Libraries
Install Mocha, Chai, and Supertest for testing:
npm install mocha chai supertest --save-dev
Create Test Directory
Create a test
directory in the root of your project:
mkdir test
Write Unit and Integration Tests
Create a test file named test.js
in the test
directory:
const request = require('supertest');
const { expect } = require('chai');
const app = require('../server'); // Adjust the path as necessary
describe('API Tests', function () {
let token;
before(function (done) {
request(app)
.post('/api/employees/login')
.send({ email: 'admin@example.com', password: 'password123' })
.end((err, res) => {
token = res.body.token;
done();
});
});
it('should create a new customer', function (done) {
request(app)
.post('/api/customers')
.set('Authorization', `Bearer ${token}`)
.send({
firstName: 'John',
lastName: 'Doe',
email: 'john.doe@example.com',
phone: '1234567890',
address: '123 Main St',
})
.expect(201)
.end((err, res) => {
expect(res.body).to.have.property('message', 'Customer created successfully');
done();
});
});
it('should get all customers', function (done) {
request(app)
.get('/api/customers')
.set('Authorization', `Bearer ${token}`)
.expect(200)
.end((err, res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('data').that.is.an('array');
done();
});
});
// Add more tests for other endpoints as needed
});
Running Tests
Add a test script to package.json
:
"scripts": {
"test": "mocha"
}
Run the tests:
npm test
Step 3: Continuous Integration (CI)
Set up a CI pipeline to run tests automatically on each push. For this example, we’ll use GitHub Actions.
Create GitHub Actions Workflow
Create a directory named .github/workflows
and a file named nodejs.yml
inside it:
name: Node.js CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [14.x, 16.x]
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up Node.js
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
- name: Install dependencies
run: npm install
- name: Run tests
run: npm test
This configuration will run your tests on the latest Node.js versions 14.x and 16.x whenever you push code to the main
branch or create a pull request targeting the main
branch.
In this module, we covered the deployment of the application to Heroku and set up a testing environment using Mocha, Chai, and Supertest. We also implemented a CI pipeline using GitHub Actions to run tests automatically on each push. This completes the development process for our car repair backend shop app. With these steps, you can ensure that your application is deployed, tested, and continuously integrated to maintain high code quality and reliability.

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.