Module One
Installation of Apps, Servers, and Dependencies for a CRUD Notes App with User Verification
In this article, we will guide you through the process of building a Customer Relationship Management (CRM) system using Node.js and Express. This will be a backend-focused project, so no frontend development will be covered. Our journey will be divided into several modules, each focusing on a specific part of the development process.
Module 1: Installation and Setup
Step 1: Prerequisites
Before we start, ensure you have the following software installed on your machine:
- Node.js: Download and install from Node.js official site.
- npm: Node Package Manager, which comes with Node.js.
- MongoDB: Download and install from MongoDB official site.
- Git: Download and install from Git official site.
- Visual Studio Code: Download and install from VS Code official site.
Step 2: Initializing the Project
Create a project directory:
mkdir crm-backend
cd crm-backend
Initialize npm: Initialize a new npm project by running the following command:
npm init -y
Install required dependencies:
npm install express mongoose dotenv bcryptjs jsonwebtoken cookie-parser
These are the dependencies necessary for running the application in any environment.
express:
- What: A fast, unopinionated, minimalist web framework for Node.js.
- Use: It provides robust features for web and mobile applications, including routing, middleware, and HTTP utility methods.
mongoose:
- What: An Object Data Modeling (ODM) library for MongoDB and Node.js.
- Use: It provides a straightforward, schema-based solution to model your application data, making interactions with MongoDB easier.
dotenv:
- What: A zero-dependency module that loads environment variables from a
.env
file intoprocess.env
. - Use: It helps manage environment-specific configurations securely and conveniently.
- What: A zero-dependency module that loads environment variables from a
bcryptjs:
- What: A library to help you hash passwords.
- Use: It provides secure hashing and salting of passwords, which is essential for authentication.
jsonwebtoken:
- What: A library to sign, verify, and decode JSON Web Tokens (JWT).
- Use: It is used for secure user authentication and authorization by issuing and validating tokens.
cookie-parser:
- What: A middleware to parse cookies attached to client requests.
- Use: It helps in managing cookies, which can be used for session management, tracking, and other purposes.
Install development dependencies:
npm install --save-dev nodemon eslint
These dependencies are only needed during the development phase to improve the development workflow and ensure code quality.
nodemon:
- What: A tool that helps develop node.js-based applications by automatically restarting the node application when file changes are detected.
- Use: It speeds up development by automatically restarting the server whenever you make changes to the codebase.
eslint:
- What: A tool for identifying and reporting on patterns found in ECMAScript/JavaScript code.
- Use: It helps maintain code quality and consistency by identifying problematic patterns and ensuring adherence to coding standards.
Step 3: Setting Up the Server
Create the server file: In the root directory, create a file named
server.js
and add the following code:
const express = require('express');
const mongoose = require('mongoose');
const dotenv = require('dotenv');
dotenv.config();
const app = express();
app.use(express.json());
const PORT = process.env.PORT || 5000;
mongoose.connect(process.env.MONGO_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
}).then(() => {
console.log('Connected to MongoDB');
}).catch((err) => {
console.error('Failed to connect to MongoDB', err);
});
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
Create a .env
file: In the root directory, create a .env
file and add your environment variables:
PORT=5000
MONGO_URI=your_mongodb_connection_string
Step 4: Setting Up Git and GitHub
Initialize Git:
git init
2. Create a .gitignore
file: In the root directory, create a .gitignore
file and add the following:
node_modules/
.env
Push to GitHub:
- Create a new repository on GitHub.
- Link your local repository to GitHub
git remote add origin https://github.com/yourusername/crm-backend.git
git branch -M main
git add .
git commit -m "Initial commit"
git push -u origin main
Connecting to GitHub via Visual Studio Code
Open your project in Visual Studio Code.
Install GitHub extension (if not already installed).
Clone the repository:
- Use the command palette (
Ctrl+Shift+P
orCmd+Shift+P
) and typeGit: Clone
. - Enter your repository URL.
- Use the command palette (
Commit and push changes:
- Use the source control panel to commit and push changes directly from VS Code.
With this, our initial setup and project structure are complete. In the next module, we will focus on designing our database schema.
In the following sections, we will cover the database design, user authentication, model creation, routes and controllers, advanced filtering and pagination, and finally, testing and deployment.
Module 2: Database Design for CRM
In this module, we will design the database schema for our CRM system using MongoDB. The database will include entities such as Customer, Product, Order, Order Detail, Employee, and Activity. We’ll also establish the relationships between these entities.
Entities and Attributes
Customer:
CustomerID
(Primary Key): Unique identifier for each customer.Name
: Name of the customer.Email
: Email address of the customer.Phone
: Phone number of the customer.Address
: Address of the customer (including city, state, postal code, etc.).
Product:
ProductID
(Primary Key): Unique identifier for each product.Name
: Name of the product.Description
: Description of the product.Price
: Price of the product.Category
: Category of the product.Brand
: Brand of the product.QuantityInStock
: Quantity of the product in stock.
Order:
OrderID
(Primary Key): Unique identifier for each order.CustomerID
(Foreign Key): Refers to the customer who placed the order.OrderDate
: Date when the order was placed.TotalAmount
: Total amount of the order.Status
: Status of the order.PaymentMethod
: Payment method used for the order.ShippingAddress
: Shipping address for the order.
Order Detail:
OrderDetailID
(Primary Key): Unique identifier for each order detail.OrderID
(Foreign Key): Refers to the order to which this detail belongs.ProductID
(Foreign Key): Refers to the product ordered.Quantity
: Quantity of the product ordered.UnitPrice
: Price per unit of the product.Subtotal
: Subtotal for the order detail.Discount
: Discount applied to the order detail.
Employee:
EmployeeID
(Primary Key): Unique identifier for each employee.Name
: Name of the employee.Email
: Email address of the employee.Phone
: Phone number of the employee.Position
: Position or job title of the employee.Department
: Department of the employee.HireDate
: Hire date of the employee.
Activity:
ActivityID
(Primary Key): Unique identifier for each activity.Type
: Type or category of the activity.Description
: Description of the activity.Date
: Date when the activity took place.Time
: Time when the activity took place.Location
: Location of the activity.Duration
: Duration of the activity.Participants
: Participants involved in the activity.
Relationships
Customer – Order Relationship:
- One-to-many relationship.
- A customer can place multiple orders.
- Implemented with a foreign key (
CustomerID
) in the Order table.
Order – Order Detail Relationship:
- One-to-many relationship.
- An order can have multiple order details.
- Implemented with a foreign key (
OrderID
) in the Order Detail table.
Product – Order Detail Relationship:
- One-to-many relationship.
- A product can be included in multiple order details.
- Implemented with a foreign key (
ProductID
) in the Order Detail table.
Employee – Order Detail Relationship:
- One-to-many relationship.
- An employee can handle multiple order details.
Employee – Activity Relationship:
- One-to-many relationship.
- An employee can perform multiple activities.
MongoDB Schema Design
Now, let’s create the MongoDB schema for each of the entities.
1. Customer Schema
const mongoose = require('mongoose');
const customerSchema = new mongoose.Schema({
name: { type: String, required: true },
email: { type: String, required: true, unique: true },
phone: { type: String, required: true },
address: {
street: { type: String, required: true },
city: { type: String, required: true },
state: { type: String, required: true },
postalCode: { type: String, required: true }
}
}, { timestamps: true });
const Customer = mongoose.model('Customer', customerSchema);
module.exports = Customer;
Product Schema
const mongoose = require('mongoose');
const productSchema = new mongoose.Schema({
name: { type: String, required: true },
description: { type: String, required: true },
price: { type: Number, required: true },
category: { type: String, required: true },
brand: { type: String, required: true },
quantityInStock: { type: Number, required: true }
}, { timestamps: true });
const Product = mongoose.model('Product', productSchema);
module.exports = Product;
Order Schema
const mongoose = require('mongoose');
const orderSchema = new mongoose.Schema({
customer: { type: mongoose.Schema.Types.ObjectId, ref: 'Customer', required: true },
orderDate: { type: Date, required: true },
totalAmount: { type: Number, required: true },
status: { type: String, required: true },
paymentMethod: { type: String, required: true },
shippingAddress: {
street: { type: String, required: true },
city: { type: String, required: true },
state: { type: String, required: true },
postalCode: { type: String, required: true }
}
}, { timestamps: true });
const Order = mongoose.model('Order', orderSchema);
module.exports = Order;
Order Detail Schema
const mongoose = require('mongoose');
const orderDetailSchema = new mongoose.Schema({
order: { type: mongoose.Schema.Types.ObjectId, ref: 'Order', required: true },
product: { type: mongoose.Schema.Types.ObjectId, ref: 'Product', required: true },
quantity: { type: Number, required: true },
unitPrice: { type: Number, required: true },
subtotal: { type: Number, required: true },
discount: { type: Number, default: 0 }
}, { timestamps: true });
const OrderDetail = mongoose.model('OrderDetail', orderDetailSchema);
module.exports = OrderDetail;
Employee Schema
const mongoose = require('mongoose');
const employeeSchema = new mongoose.Schema({
name: { type: String, required: true },
email: { type: String, required: true, unique: true },
phone: { type: String, required: true },
position: { type: String, required: true },
department: { type: String, required: true },
hireDate: { type: Date, required: true }
}, { timestamps: true });
const Employee = mongoose.model('Employee', employeeSchema);
module.exports = Employee;
Activity Schema
const mongoose = require('mongoose');
const activitySchema = new mongoose.Schema({
employee: { type: mongoose.Schema.Types.ObjectId, ref: 'Employee', required: true },
type: { type: String, required: true },
description: { type: String, required: true },
date: { type: Date, required: true },
time: { type: String, required: true },
location: { type: String, required: true },
duration: { type: Number, required: true },
participants: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Employee' }]
}, { timestamps: true });
const Activity = mongoose.model('Activity', activitySchema);
module.exports = Activity;
In this module, we have defined the entities and their attributes for our CRM system, established the relationships between these entities, and created the MongoDB schemas for each entity. In the next module, we will focus on user authentication and authorization, including login and registration with error validation, full authorization and authentication using JWT, bcrypt, and cookies.
Module 3: User Authentication and Authorization
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut elit tellus, luctus nec ullamcorper mattis, pulvinar dapibus leo.
In this module, we will implement user authentication and authorization. We will ensure that:
- Customers cannot access other customers’ data.
- Employees cannot access customers managed by other employees.
- Admins can access all data.
We will use JWT (JSON Web Tokens) for authentication, bcrypt for hashing passwords, and middleware for handling authorization. Additionally, we will implement error handling.
Step 1: Setting Up User Registration and Login
First, we need to create user models, controllers, and routes for registration and login.
1. User Model
We will have three types of users: customers, employees, and admins. For simplicity, we will use a single User model with a role
field to differentiate between these types.
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
const userSchema = new mongoose.Schema({
name: { type: String, required: true },
email: { type: String, required: true, unique: true },
password: { type: String, required: true },
role: { type: String, enum: ['customer', 'employee', 'admin'], required: true },
employee: { type: mongoose.Schema.Types.ObjectId, ref: 'Employee' } // Only for customers
}, { timestamps: true });
// Hash password before saving
userSchema.pre('save', async function (next) {
if (!this.isModified('password')) return next();
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.password, salt);
next();
});
// Compare password
userSchema.methods.matchPassword = async function (enteredPassword) {
return await bcrypt.compare(enteredPassword, this.password);
};
const User = mongoose.model('User', userSchema);
module.exports = User;
2. User Controller
Create a controller for handling user registration and login.
const User = require('../models/userModel');
const jwt = require('jsonwebtoken');
// Generate JWT Token
const generateToken = (id) => {
return jwt.sign({ id }, process.env.JWT_SECRET, { expiresIn: '30d' });
};
// Register User
exports.registerUser = async (req, res) => {
const { name, email, password, role, employee } = req.body;
if (!name || !email || !password || !role) {
return res.status(400).json({ message: 'Please fill all fields' });
}
const userExists = await User.findOne({ email });
if (userExists) {
return res.status(400).json({ message: 'User already exists' });
}
const user = await User.create({
name,
email,
password,
role,
employee: role === 'customer' ? employee : null
});
if (user) {
res.status(201).json({
_id: user._id,
name: user.name,
email: user.email,
role: user.role,
token: generateToken(user._id)
});
} else {
res.status(400).json({ message: 'Invalid user data' });
}
};
// Login User
exports.authUser = async (req, res) => {
const { email, password } = req.body;
const user = await User.findOne({ email });
if (user && (await user.matchPassword(password))) {
res.json({
_id: user._id,
name: user.name,
email: user.email,
role: user.role,
token: generateToken(user._id)
});
} else {
res.status(401).json({ message: 'Invalid email or password' });
}
};
3. User Routes
Create routes for user registration and login.
const express = require('express');
const router = express.Router();
const { registerUser, authUser } = require('../controllers/userController');
router.post('/register', registerUser);
router.post('/login', authUser);
module.exports = router;
Step 2: Middleware for Authentication and Authorization
Create middleware to protect routes and ensure users can only access their own data or data they are authorized to access.
1. Auth Middleware
Middleware to authenticate users using JWT.
const jwt = require('jsonwebtoken');
const User = require('../models/userModel');
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' });
}
}
if (!token) {
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 employeeOrAdmin = (req, res, next) => {
if (req.user && (req.user.role === 'employee' || req.user.role === 'admin')) {
next();
} else {
res.status(401).json({ message: 'Not authorized' });
}
};
module.exports = { protect, admin, employeeOrAdmin };
Step 3: Protecting Routes
Use the middleware to protect routes and ensure proper access control.
Example: Protecting Customer Routes
- Customer Controller (only accessible by the admin and the managing employee)
const Customer = require('../models/customerModel');
// Get All Customers (Admin only)
exports.getAllCustomers = async (req, res) => {
const customers = await Customer.find({});
res.json(customers);
};
// Get Customer by ID (Admin and assigned employee)
exports.getCustomerById = async (req, res) => {
const customer = await Customer.findById(req.params.id);
if (customer) {
// Ensure the employee is assigned to the customer
if (req.user.role === 'admin' || customer.employee.toString() === req.user._id.toString()) {
res.json(customer);
} else {
res.status(401).json({ message: 'Not authorized to access this customer' });
}
} else {
res.status(404).json({ message: 'Customer not found' });
}
};
// Add Customer (Admin and assigned employee)
exports.addCustomer = async (req, res) => {
const { name, email, phone, address, employee } = req.body;
const customer = new Customer({
name,
email,
phone,
address,
employee
});
const createdCustomer = await customer.save();
res.status(201).json(createdCustomer);
};
2. Customer Routes
const express = require('express');
const router = express.Router();
const { getAllCustomers, getCustomerById, addCustomer } = require('../controllers/customerController');
const { protect, admin, employeeOrAdmin } = require('../middleware/authMiddleware');
router.route('/')
.get(protect, admin, getAllCustomers)
.post(protect, employeeOrAdmin, addCustomer);
router.route('/:id')
.get(protect, employeeOrAdmin, getCustomerById);
module.exports = router;
Step 4: Error Handling Middleware
Create a middleware to handle errors consistently.
const notFound = (req, res, next) => {
const error = new Error(`Not Found - ${req.originalUrl}`);
res.status(404);
next(error);
};
const errorHandler = (err, req, res, next) => {
const statusCode = res.statusCode === 200 ? 500 : res.statusCode;
res.status(statusCode);
res.json({
message: err.message,
stack: process.env.NODE_ENV === 'production' ? null : err.stack
});
};
module.exports = { notFound, errorHandler };
Step 5: Integrate Middleware in Server
Update server.js
to include the middleware and routes.
const express = require('express');
const mongoose = require('mongoose');
const dotenv = require('dotenv');
const userRoutes = require('./routes/userRoutes');
const customerRoutes = require('./routes/customerRoutes');
const { notFound, errorHandler } = require('./middleware/errorMiddleware');
dotenv.config();
const app = express();
app.use(express.json());
const PORT = process.env.PORT || 5000;
mongoose.connect(process.env.MONGO_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
}).then(() => {
console.log('Connected to MongoDB');
}).catch((err) => {
console.error('Failed to connect to MongoDB', err);
});
app.use('/api/users', userRoutes);
app.use('/api/customers', customerRoutes);
app.use(notFound);
app.use(errorHandler);
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
In this module, we implemented user authentication and authorization, ensuring secure access to customer data. We also added middleware for error handling and protected routes based on user roles. This setup ensures that customers cannot access other customers’ data, employees cannot access customers managed by other employees, and admins have full access. In the next module, we will focus on creating the remaining models for our CRM system.
Module 4: Creating Models
In this module, we will create the remaining models for our CRM system, which include Customer, Product, Order, Order Detail, Employee, and Activity. Each model will be defined with the necessary fields and relationships.
1. Customer Model
The Customer model represents individuals or organizations raising a query.
const mongoose = require('mongoose');
const customerSchema = new mongoose.Schema({
name: { type: String, required: true },
email: { type: String, required: true, unique: true },
phone: { type: String, required: true },
address: {
street: { type: String, required: true },
city: { type: String, required: true },
state: { type: String, required: true },
postalCode: { type: String, required: true }
},
employee: { type: mongoose.Schema.Types.ObjectId, ref: 'Employee', required: true }
}, { timestamps: true });
const Customer = mongoose.model('Customer', customerSchema);
module.exports = Customer;
2. Product Model
The Product model represents the goods that are ordered.
const mongoose = require('mongoose');
const productSchema = new mongoose.Schema({
name: { type: String, required: true },
description: { type: String, required: true },
price: { type: Number, required: true },
category: { type: String, required: true },
brand: { type: String, required: true },
quantityInStock: { type: Number, required: true }
}, { timestamps: true });
const Product = mongoose.model('Product', productSchema);
module.exports = Product;
3. Order Model
The Order model represents the information of the order like date and amount.
const mongoose = require('mongoose');
const orderSchema = new mongoose.Schema({
customer: { type: mongoose.Schema.Types.ObjectId, ref: 'Customer', required: true },
orderDate: { type: Date, required: true },
totalAmount: { type: Number, required: true },
status: { type: String, required: true },
paymentMethod: { type: String, required: true },
shippingAddress: {
street: { type: String, required: true },
city: { type: String, required: true },
state: { type: String, required: true },
postalCode: { type: String, required: true }
}
}, { timestamps: true });
const Order = mongoose.model('Order', orderSchema);
module.exports = Order;
4. Order Detail Model
The Order Detail model represents the information of the ordered product.
const mongoose = require('mongoose');
const orderDetailSchema = new mongoose.Schema({
order: { type: mongoose.Schema.Types.ObjectId, ref: 'Order', required: true },
product: { type: mongoose.Schema.Types.ObjectId, ref: 'Product', required: true },
quantity: { type: Number, required: true },
unitPrice: { type: Number, required: true },
subtotal: { type: Number, required: true },
discount: { type: Number, default: 0 }
}, { timestamps: true });
const OrderDetail = mongoose.model('OrderDetail', orderDetailSchema);
module.exports = OrderDetail;
5. Employee Model
The Employee model represents personnel involved in managing and executing the queries.
const mongoose = require('mongoose');
const employeeSchema = new mongoose.Schema({
name: { type: String, required: true },
email: { type: String, required: true, unique: true },
phone: { type: String, required: true },
position: { type: String, required: true },
department: { type: String, required: true },
hireDate: { type: Date, required: true }
}, { timestamps: true });
const Employee = mongoose.model('Employee', employeeSchema);
module.exports = Employee;
6. Activity Model
The Activity model represents different activities that are performed by different employees.
const mongoose = require('mongoose');
const activitySchema = new mongoose.Schema({
employee: { type: mongoose.Schema.Types.ObjectId, ref: 'Employee', required: true },
type: { type: String, required: true },
description: { type: String, required: true },
date: { type: Date, required: true },
time: { type: String, required: true },
location: { type: String, required: true },
duration: { type: Number, required: true },
participants: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Employee' }]
}, { timestamps: true });
const Activity = mongoose.model('Activity', activitySchema);
module.exports = Activity;
We have now created the models for our CRM system. Each model corresponds to a specific entity in our database and includes the necessary fields and relationships. In the next module, we will focus on creating controllers and routes for these models to handle CRUD operations and business logic.
Module 5: Creating Controllers and Routes
In this module, we will create controllers and routes for the models we defined in Module 4. These controllers will handle the CRUD operations and business logic, while the routes will define the API endpoints.
1. Customer Controller and Routes
Customer Controller
const Customer = require('../models/customerModel');
// Get All Customers (Admin only)
exports.getAllCustomers = async (req, res) => {
try {
const customers = await Customer.find({});
res.json(customers);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Get Customer by ID (Admin and assigned employee)
exports.getCustomerById = async (req, res) => {
try {
const customer = await Customer.findById(req.params.id);
if (customer) {
if (req.user.role === 'admin' || customer.employee.toString() === req.user._id.toString()) {
res.json(customer);
} else {
res.status(401).json({ message: 'Not authorized to access this customer' });
}
} else {
res.status(404).json({ message: 'Customer not found' });
}
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Add Customer (Admin and assigned employee)
exports.addCustomer = async (req, res) => {
const { name, email, phone, address, employee } = req.body;
try {
const customer = new Customer({
name,
email,
phone,
address,
employee
});
const createdCustomer = await customer.save();
res.status(201).json(createdCustomer);
} catch (error) {
res.status(400).json({ message: error.message });
}
};
// Update Customer (Admin and assigned employee)
exports.updateCustomer = async (req, res) => {
const { name, email, phone, address, employee } = req.body;
try {
const customer = await Customer.findById(req.params.id);
if (customer) {
if (req.user.role === 'admin' || customer.employee.toString() === req.user._id.toString()) {
customer.name = name || customer.name;
customer.email = email || customer.email;
customer.phone = phone || customer.phone;
customer.address = address || customer.address;
customer.employee = employee || customer.employee;
const updatedCustomer = await customer.save();
res.json(updatedCustomer);
} else {
res.status(401).json({ message: 'Not authorized to update this customer' });
}
} else {
res.status(404).json({ message: 'Customer not found' });
}
} catch (error) {
res.status(400).json({ message: error.message });
}
};
// Delete Customer (Admin only)
exports.deleteCustomer = async (req, res) => {
try {
const customer = await Customer.findById(req.params.id);
if (customer) {
await customer.remove();
res.json({ message: 'Customer removed' });
} else {
res.status(404).json({ message: 'Customer not found' });
}
} catch (error) {
res.status(500).json({ message: error.message });
}
};
Customer Routes
const express = require('express');
const router = express.Router();
const { getAllCustomers, getCustomerById, addCustomer, updateCustomer, deleteCustomer } = require('../controllers/customerController');
const { protect, admin, employeeOrAdmin } = require('../middleware/authMiddleware');
router.route('/')
.get(protect, admin, getAllCustomers)
.post(protect, employeeOrAdmin, addCustomer);
router.route('/:id')
.get(protect, employeeOrAdmin, getCustomerById)
.put(protect, employeeOrAdmin, updateCustomer)
.delete(protect, admin, deleteCustomer);
module.exports = router;
2. Product Controller and Routes
Product Controller
const Product = require('../models/productModel');
// Get All Products
exports.getAllProducts = async (req, res) => {
try {
const products = await Product.find({});
res.json(products);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Get Product by ID
exports.getProductById = async (req, res) => {
try {
const product = await Product.findById(req.params.id);
if (product) {
res.json(product);
} else {
res.status(404).json({ message: 'Product not found' });
}
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Add Product (Admin only)
exports.addProduct = async (req, res) => {
const { name, description, price, category, brand, quantityInStock } = req.body;
try {
const product = new Product({
name,
description,
price,
category,
brand,
quantityInStock
});
const createdProduct = await product.save();
res.status(201).json(createdProduct);
} catch (error) {
res.status(400).json({ message: error.message });
}
};
// Update Product (Admin only)
exports.updateProduct = async (req, res) => {
const { name, description, price, category, brand, quantityInStock } = req.body;
try {
const product = await Product.findById(req.params.id);
if (product) {
product.name = name || product.name;
product.description = description || product.description;
product.price = price || product.price;
product.category = category || product.category;
product.brand = brand || product.brand;
product.quantityInStock = quantityInStock || product.quantityInStock;
const updatedProduct = await product.save();
res.json(updatedProduct);
} else {
res.status(404).json({ message: 'Product not found' });
}
} catch (error) {
res.status(400).json({ message: error.message });
}
};
// Delete Product (Admin only)
exports.deleteProduct = async (req, res) => {
try {
const product = await Product.findById(req.params.id);
if (product) {
await product.remove();
res.json({ message: 'Product removed' });
} else {
res.status(404).json({ message: 'Product not found' });
}
} catch (error) {
res.status(500).json({ message: error.message });
}
};
Product Routes
const express = require('express');
const router = express.Router();
const { getAllProducts, getProductById, addProduct, updateProduct, deleteProduct } = require('../controllers/productController');
const { protect, admin } = require('../middleware/authMiddleware');
router.route('/')
.get(getAllProducts)
.post(protect, admin, addProduct);
router.route('/:id')
.get(getProductById)
.put(protect, admin, updateProduct)
.delete(protect, admin, deleteProduct);
module.exports = router;
3. Order Controller and Routes
Order Controller
const Order = require('../models/orderModel');
const OrderDetail = require('../models/orderDetailModel');
// Get All Orders (Admin only)
exports.getAllOrders = async (req, res) => {
try {
const orders = await Order.find({}).populate('customer', 'name email');
res.json(orders);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Get Order by ID (Admin and assigned employee)
exports.getOrderById = async (req, res) => {
try {
const order = await Order.findById(req.params.id).populate('customer', 'name email');
if (order) {
if (req.user.role === 'admin' || order.customer.employee.toString() === req.user._id.toString()) {
res.json(order);
} else {
res.status(401).json({ message: 'Not authorized to access this order' });
}
} else {
res.status(404).json({ message: 'Order not found' });
}
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Add Order (Admin and assigned employee)
exports.addOrder = async (req, res) => {
const { customer, orderDate, totalAmount, status, paymentMethod, shippingAddress, orderDetails } = req.body;
try {
const order = new Order({
customer,
orderDate,
totalAmount,
status,
paymentMethod,
shippingAddress
});
const createdOrder = await order.save();
for (const detail of orderDetails) {
const orderDetail = new OrderDetail({
order: createdOrder._id,
product: detail.product,
quantity: detail.quantity,
unitPrice: detail.unitPrice,
subtotal: detail.subtotal,
discount: detail.discount
});
await orderDetail.save();
}
res.status(201).json(createdOrder);
} catch (error) {
res.status(400).json({ message: error.message });
}
};
// Update Order (Admin and assigned employee)
exports.updateOrder = async (req, res) => {
const { customer, orderDate, totalAmount, status, paymentMethod, shippingAddress } = req.body;
try {
const order = await Order.findById(req.params.id);
if (order) {
if (req.user.role === 'admin' || order.customer.employee.toString() === req.user._id.toString()) {
order.customer = customer || order.customer;
order.orderDate = orderDate || order.orderDate;
order.totalAmount = totalAmount || order.totalAmount;
order.status = status || order.status;
order.paymentMethod = paymentMethod || order.paymentMethod;
order.shippingAddress = shippingAddress || order.shippingAddress;
const updatedOrder = await order.save();
res.json(updatedOrder);
} else {
res.status(401).json({ message: 'Not authorized to update this order' });
}
} else {
res.status(404).json({ message: 'Order not found' });
}
} catch (error) {
res.status(400).json({ message: error.message });
}
};
// Delete Order (Admin only)
exports.deleteOrder = async (req, res) => {
try {
const order = await Order.findById(req.params.id);
if (order) {
await OrderDetail.deleteMany({ order: order._id });
await order.remove();
res.json({ message: 'Order removed' });
} else {
res.status(404).json({ message: 'Order not found' });
}
} catch (error) {
res.status(500).json({ message: error.message });
}
};
Order Routes
const express = require('express');
const router = express.Router();
const { getAllOrders, getOrderById, addOrder, updateOrder, deleteOrder } = require('../controllers/orderController');
const { protect, admin, employeeOrAdmin } = require('../middleware/authMiddleware');
router.route('/')
.get(protect, admin, getAllOrders)
.post(protect, employeeOrAdmin, addOrder);
router.route('/:id')
.get(protect, employeeOrAdmin, getOrderById)
.put(protect, employeeOrAdmin, updateOrder)
.delete(protect, admin, deleteOrder);
module.exports = router;
4. Order Detail Controller and Routes
Order Details are managed as part of Orders, so we don’t need separate controllers or routes for them. They are included within the Order controller.
5. Employee Controller and Routes
Employee Controller
const Employee = require('../models/employeeModel');
// Get All Employees (Admin only)
exports.getAllEmployees = async (req, res) => {
try {
const employees = await Employee.find({});
res.json(employees);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Get Employee by ID (Admin only)
exports.getEmployeeById = async (req, res) => {
try {
const employee = await Employee.findById(req.params.id);
if (employee) {
res.json(employee);
} else {
res.status(404).json({ message: 'Employee not found' });
}
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Add Employee (Admin only)
exports.addEmployee = async (req, res) => {
const { name, email, phone, position, department, hireDate } = req.body;
try {
const employee = new Employee({
name,
email,
phone,
position,
department,
hireDate
});
const createdEmployee = await employee.save();
res.status(201).json(createdEmployee);
} catch (error) {
res.status(400).json({ message: error.message });
}
};
// Update Employee (Admin only)
exports.updateEmployee = async (req, res) => {
const { name, email, phone, position, department, hireDate } = req.body;
try {
const employee = await Employee.findById(req.params.id);
if (employee) {
employee.name = name || employee.name;
employee.email = email || employee.email;
employee.phone = phone || employee.phone;
employee.position = position || employee.position;
employee.department = department || employee.department;
employee.hireDate = hireDate || employee.hireDate;
const updatedEmployee = await employee.save();
res.json(updatedEmployee);
} else {
res.status(404).json({ message: 'Employee not found' });
}
} catch (error) {
res.status(400).json({ message: error.message });
}
};
// Delete Employee (Admin only)
exports.deleteEmployee = async (req, res) => {
try {
const employee = await Employee.findById(req.params.id);
if (employee) {
await employee.remove();
res.json({ message: 'Employee removed' });
} else {
res.status(404).json({ message: 'Employee not found' });
}
} catch (error) {
res.status(500).json({ message: error.message });
}
};
Employee Routes
const express = require('express');
const router = express.Router();
const { getAllEmployees, getEmployeeById, addEmployee, updateEmployee, deleteEmployee } = require('../controllers/employeeController');
const { protect, admin } = require('../middleware/authMiddleware');
router.route('/')
.get(protect, admin, getAllEmployees)
.post(protect, admin, addEmployee);
router.route('/:id')
.get(protect, admin, getEmployeeById)
.put(protect, admin, updateEmployee)
.delete(protect, admin, deleteEmployee);
module.exports = router;
6. Activity Controller and Routes
Activity Controller
const Activity = require('../models/activityModel');
// Get All Activities (Admin and employee)
exports.getAllActivities = async (req, res) => {
try {
const activities = await Activity.find({}).populate('employee', 'name email');
res.json(activities);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Get Activity by ID (Admin and assigned employee)
exports.getActivityById = async (req, res) => {
try {
const activity = await Activity.findById(req.params.id).populate('employee', 'name email');
if (activity) {
if (req.user.role === 'admin' || activity.employee._id.toString() === req.user._id.toString()) {
res.json(activity);
} else {
res.status(401).json({ message: 'Not authorized to access this activity' });
}
} else {
res.status(404).json({ message: 'Activity not found' });
}
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Add Activity (Admin and employee)
exports.addActivity = async (req, res) => {
const { employee, type, description, date, time, location, duration, participants } = req.body;
try {
const activity = new Activity({
employee,
type,
description,
date,
time,
location,
duration,
participants
});
const createdActivity = await activity.save();
res.status(201).json(createdActivity);
} catch (error) {
res.status(400).json({ message: error.message });
}
};
// Update Activity (Admin and assigned employee)
exports.updateActivity = async (req, res) => {
const { employee, type, description, date, time, location, duration, participants } = req.body;
try {
const activity = await Activity.findById(req.params.id);
if (activity) {
if (req.user.role === 'admin' || activity.employee._id.toString() === req.user._id.toString()) {
activity.employee = employee || activity.employee;
activity.type = type || activity.type;
activity.description = description || activity.description;
activity.date = date || activity.date;
activity.time = time || activity.time;
activity.location = location || activity.location;
activity.duration = duration || activity.duration;
activity.participants = participants || activity.participants;
const updatedActivity = await activity.save();
res.json(updatedActivity);
} else {
res.status(401).json({ message: 'Not authorized to update this activity' });
}
} else {
res.status(404).json({ message: 'Activity not found' });
}
} catch (error) {
res.status(400).json({ message: error.message });
}
};
// Delete Activity (Admin only)
exports.deleteActivity = async (req, res) => {
try {
const activity = await Activity.findById(req.params.id);
if (activity) {
await activity.remove();
res.json({ message: 'Activity removed' });
} else {
res.status(404).json({ message: 'Activity not found' });
}
} catch (error) {
res.status(500).json({ message: error.message });
}
};
Activity Routes
const express = require('express');
const router = express.Router();
const { getAllActivities, getActivityById, addActivity, updateActivity, deleteActivity } = require('../controllers/activityController');
const { protect, employeeOrAdmin } = require('../middleware/authMiddleware');
router.route('/')
.get(protect, employeeOrAdmin, getAllActivities)
.post(protect, employeeOrAdmin, addActivity);
router.route('/:id')
.get(protect, employeeOrAdmin, getActivityById)
.put(protect, employeeOrAdmin, updateActivity)
.delete(protect, employeeOrAdmin, deleteActivity);
module.exports = router;
In this module, we have created controllers and routes for our CRM system, handling CRUD operations and business logic for Customers, Products, Orders, Employees, and Activities. In the next module, we will implement advanced features such as pagination, filtering, and search functionalities.
Module 6: Pagination, filtering and search
In this module, we will implement advanced features such as pagination, filtering, and search functionalities. These features will allow users to retrieve data more efficiently and perform specific queries.
Customer Controller
const Customer = require('../models/customerModel');
// Get all customers (admin only)
exports.getAllCustomers = async (req, res) => {
try {
// Create a copy of query parameters and exclude pagination/sorting fields
const queryObj = { ...req.query };
const excludedFields = ['page', 'limit', 'sort', 'fields', 'search'];
excludedFields.forEach(el => delete queryObj[el]);
// Convert query object to a string and add MongoDB operators
let queryStr = JSON.stringify(queryObj);
queryStr = queryStr.replace(/\b(gte|gt|lte|lt|in)\b/g, match => `$${match}`);
// Initialize the query with the parsed query string
let query = Customer.find(JSON.parse(queryStr));
// Add search functionality if 'search' parameter is present
if (req.query.search) {
const searchString = req.query.search;
query = query.find({
$or: [
{ name: { $regex: searchString, $options: 'i' } },
{ email: { $regex: searchString, $options: 'i' } },
{ phone: { $regex: searchString, $options: 'i' } }
]
});
}
// Add sorting functionality if 'sort' parameter is present
if (req.query.sort) {
const sortBy = req.query.sort.split(',').join(' ');
query = query.sort(sortBy);
} else {
query = query.sort('-createdAt');
}
// Select specific fields if 'fields' parameter is present
if (req.query.fields) {
const fields = req.query.fields.split(',').join(' ');
query = query.select(fields);
} else {
query = query.select('-__v');
}
// Handle pagination
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
const startIndex = (page - 1) * limit;
query = query.skip(startIndex).limit(limit);
// Execute the query and return the results
const customers = await query;
res.json(customers);
} catch (error) {
// Handle any errors that occur during the query
res.status(500).json({ message: error.message });
}
};
// Other customer-related methods...
Product Controller
const Product = require('../models/productModel');
// Get all products
exports.getAllProducts = async (req, res) => {
try {
// Create a copy of query parameters and exclude pagination/sorting fields
const queryObj = { ...req.query };
const excludedFields = ['page', 'limit', 'sort', 'fields', 'search'];
excludedFields.forEach(el => delete queryObj[el]);
// Convert query object to a string and add MongoDB operators
let queryStr = JSON.stringify(queryObj);
queryStr = queryStr.replace(/\b(gte|gt|lte|lt|in)\b/g, match => `$${match}`);
// Initialize the query with the parsed query string
let query = Product.find(JSON.parse(queryStr));
// Add search functionality if 'search' parameter is present
if (req.query.search) {
const searchString = req.query.search;
query = query.find({
$or: [
{ name: { $regex: searchString, $options: 'i' } },
{ description: { $regex: searchString, $options: 'i' } },
{ category: { $regex: searchString, $options: 'i' } },
{ brand: { $regex: searchString, $options: 'i' } }
]
});
}
// Add sorting functionality if 'sort' parameter is present
if (req.query.sort) {
const sortBy = req.query.sort.split(',').join(' ');
query = query.sort(sortBy);
} else {
query = query.sort('-createdAt');
}
// Select specific fields if 'fields' parameter is present
if (req.query.fields) {
const fields = req.query.fields.split(',').join(' ');
query = query.select(fields);
} else {
query = query.select('-__v');
}
// Handle pagination
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
const startIndex = (page - 1) * limit;
query = query.skip(startIndex).limit(limit);
// Execute the query and return the results
const products = await query;
res.json(products);
} catch (error) {
// Handle any errors that occur during the query
res.status(500).json({ message: error.message });
}
};
// Other product-related methods...
Order Controller
const Order = require('../models/orderModel');
// Get all orders (admin only)
exports.getAllOrders = async (req, res) => {
try {
// Create a copy of query parameters and exclude pagination/sorting fields
const queryObj = { ...req.query };
const excludedFields = ['page', 'limit', 'sort', 'fields', 'search'];
excludedFields.forEach(el => delete queryObj[el]);
// Convert query object to a string and add MongoDB operators
let queryStr = JSON.stringify(queryObj);
queryStr = queryStr.replace(/\b(gte|gt|lte|lt|in)\b/g, match => `$${match}`);
// Initialize the query with the parsed query string
let query = Order.find(JSON.parse(queryStr)).populate('customer', 'name email');
// Add search functionality if 'search' parameter is present
if (req.query.search) {
const searchString = req.query.search;
query = query.find({
$or: [
{ status: { $regex: searchString, $options: 'i' } },
{ paymentMethod: { $regex: searchString, $options: 'i' } }
]
});
}
// Add sorting functionality if 'sort' parameter is present
if (req.query.sort) {
const sortBy = req.query.sort.split(',').join(' ');
query = query.sort(sortBy);
} else {
query = query.sort('-createdAt');
}
// Select specific fields if 'fields' parameter is present
if (req.query.fields) {
const fields = req.query.fields.split(',').join(' ');
query = query.select(fields);
} else {
query = query.select('-__v');
}
// Handle pagination
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
const startIndex = (page - 1) * limit;
query = query.skip(startIndex).limit(limit);
// Execute the query and return the results
const orders = await query;
res.json(orders);
} catch (error) {
// Handle any errors that occur during the query
res.status(500).json({ message: error.message });
}
};
// Other order-related methods...
Employee Controller
const Employee = require('../models/employeeModel');
// Get all employees (admin only)
exports.getAllEmployees = async (req, res) => {
try {
// Create a copy of query parameters and exclude pagination/sorting fields
const queryObj = { ...req.query };
const excludedFields = ['page', 'limit', 'sort', 'fields', 'search'];
excludedFields.forEach(el => delete queryObj[el]);
// Convert query object to a string and add MongoDB operators
let queryStr = JSON.stringify(queryObj);
queryStr = queryStr.replace(/\b(gte|gt|lte|lt|in)\b/g, match => `$${match}`);
// Initialize the query with the parsed query string
let query = Employee.find(JSON.parse(queryStr));
// Add search functionality if 'search' parameter is present
if (req.query.search) {
const searchString = req.query.search;
query = query.find({
$or: [
{ name: { $regex: searchString, $options: 'i' } },
{ email: { $regex: searchString, $options: 'i' } },
{ phone: { $regex: searchString, $options: 'i' } },
{ position: { $regex: searchString, $options: 'i' } },
{ department: { $regex: searchString, $options: 'i' } }
]
});
}
// Add sorting functionality if 'sort' parameter is present
if (req.query.sort) {
const sortBy = req.query.sort.split(',').join(' ');
query = query.sort(sortBy);
} else {
query = query.sort('-createdAt');
}
// Select specific fields if 'fields' parameter is present
if (req.query.fields) {
const fields = req.query.fields.split(',').join(' ');
query = query.select(fields);
} else {
query = query.select('-__v');
}
// Handle pagination
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
const startIndex = (page - 1) * limit;
query = query.skip(startIndex).limit(limit);
// Execute the query and return the results
const employees = await query;
res.json(employees);
} catch (error) {
// Handle any errors that occur during the query
res.status(500).json({ message: error.message });
}
};
// Other employee-related methods...
Activity Controller
const Activity = require('../models/activityModel');
// Get all activities (admin and employee)
exports.getAllActivities = async (req, res) => {
try {
// Create a copy of query parameters and exclude pagination/sorting fields
const queryObj = { ...req.query };
const excludedFields = ['page', 'limit', 'sort', 'fields', 'search'];
excludedFields.forEach(el => delete queryObj[el]);
// Convert query object to a string and add MongoDB operators
let queryStr = JSON.stringify(queryObj);
queryStr = queryStr.replace(/\b(gte|gt|lte|lt|in)\b/g, match => `$${match}`);
// Initialize the query with the parsed query string
let query = Activity.find(JSON.parse(queryStr)).populate('employee', 'name email');
// Add search functionality if 'search' parameter is present
if (req.query.search) {
const searchString = req.query.search;
query = query.find({
$or: [
{ type: { $regex: searchString, $options: 'i' } },
{ description: { $regex: searchString, $options: 'i' } },
{ location: { $regex: searchString, $options: 'i' } }
]
});
}
// Add sorting functionality if 'sort' parameter is present
if (req.query.sort) {
const sortBy = req.query.sort.split(',').join(' ');
query = query.sort(sortBy);
} else {
query = query.sort('-createdAt');
}
// Select specific fields if 'fields' parameter is present
if (req.query.fields) {
const fields = req.query.fields.split(',').join(' ');
query = query.select(fields);
} else {
query = query.select('-__v');
}
// Handle pagination
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
const startIndex = (page - 1) * limit;
query = query.skip(startIndex).limit(limit);
// Execute the query and return the results
const activities = await query;
res.json(activities);
} catch (error) {
// Handle any errors that occur during the query
res.status(500).json({ message: error.message });
}
};
// Other activity-related methods...
Routes
With the logic now contained within the controllers, the routes can be simplified.
Customer Routes
const express = require('express');
const router = express.Router();
const { getAllCustomers, getCustomerById, addCustomer, updateCustomer, deleteCustomer } = require('../controllers/customerController');
const { protect, admin, employeeOrAdmin } = require('../middleware/authMiddleware');
// Route to get all customers (admin only) and add a new customer (admin or assigned employee)
router.route('/')
.get(protect, admin, getAllCustomers) // Admin can get all customers
.post(protect, employeeOrAdmin, addCustomer); // Admin or assigned employee can add a customer
// Route to get, update, or delete a specific customer by ID
router.route('/:id')
.get(protect, employeeOrAdmin, getCustomerById) // Admin or assigned employee can get customer details
.put(protect, employeeOrAdmin, updateCustomer) // Admin or assigned employee can update customer details
.delete(protect, admin, deleteCustomer); // Admin can delete a customer
module.exports = router;
Product Routes
const express = require('express');
const router = express.Router();
const { getAllProducts, getProductById, addProduct, updateProduct, deleteProduct } = require('../controllers/productController');
const { protect, admin } = require('../middleware/authMiddleware');
// Route to get all products and add a new product (admin only)
router.route('/')
.get(getAllProducts) // Anyone can get all products
.post(protect, admin, addProduct); // Only admin can add a product
// Route to get, update, or delete a specific product by ID
router.route('/:id')
.get(getProductById) // Anyone can get product details
.put(protect, admin, updateProduct) // Only admin can update product details
.delete(protect, admin, deleteProduct); // Only admin can delete a product
module.exports = router;
Order Routes
const express = require('express');
const router = express.Router();
const { getAllOrders, getOrderById, addOrder, updateOrder, deleteOrder } = require('../controllers/orderController');
const { protect, admin, employeeOrAdmin } = require('../middleware/authMiddleware');
// Route to get all orders (admin only) and add a new order (admin or assigned employee)
router.route('/')
.get(protect, admin, getAllOrders) // Admin can get all orders
.post(protect, employeeOrAdmin, addOrder); // Admin or assigned employee can add an order
// Route to get, update, or delete a specific order by ID
router.route('/:id')
.get(protect, employeeOrAdmin, getOrderById) // Admin or assigned employee can get order details
.put(protect, employeeOrAdmin, updateOrder) // Admin or assigned employee can update order details
.delete(protect, admin, deleteOrder); // Admin can delete an order
module.exports = router;
Employee Routes
const express = require('express');
const router = express.Router();
const { getAllEmployees, getEmployeeById, addEmployee, updateEmployee, deleteEmployee } = require('../controllers/employeeController');
const { protect, admin } = require('../middleware/authMiddleware');
// Route to get all employees and add a new employee (admin only)
router.route('/')
.get(protect, admin, getAllEmployees) // Only admin can get all employees
.post(protect, admin, addEmployee); // Only admin can add an employee
// Route to get, update, or delete a specific employee by ID
router.route('/:id')
.get(protect, admin, getEmployeeById) // Only admin can get employee details
.put(protect, admin, updateEmployee) // Only admin can update employee details
.delete(protect, admin, deleteEmployee); // Only admin can delete an employee
module.exports = router;
Activity Routes
const express = require('express');
const router = express.Router();
const { getAllActivities, getActivityById, addActivity, updateActivity, deleteActivity } = require('../controllers/activityController');
const { protect, employeeOrAdmin } = require('../middleware/authMiddleware');
// Route to get all activities and add a new activity (admin and assigned employees)
router.route('/')
.get(protect, employeeOrAdmin, getAllActivities) // Admin and assigned employees can get all activities
.post(protect, employeeOrAdmin, addActivity); // Admin and assigned employees can add an activity
// Route to get, update, or delete a specific activity by ID
router.route('/:id')
.get(protect, employeeOrAdmin, getActivityById) // Admin and assigned employees can get activity details
.put(protect, employeeOrAdmin, updateActivity) // Admin and assigned employees can update activity details
.delete(protect, employeeOrAdmin, deleteActivity); // Admin and assigned employees can delete an activity
module.exports = router;
We have now moved the logic for search, filtering, and pagination into the respective controllers, keeping the routes clean and focusing on the API endpoint definitions. This setup enhances code organization and maintainability. In the next module, we will focus on testing and deploying the application.
Module 7: Testing and Deployment
In this final module, we will cover how to test and deploy our CRM backend. Testing ensures the reliability and correctness of our application, while deployment allows us to make our application accessible to users.
Step 1: Testing
We’ll use Jest and Supertest for testing our application. Jest is a JavaScript testing framework, and Supertest is a library for testing HTTP endpoints.
1. Install Testing Libraries
First, install Jest and Supertest:
npm install --save-dev jest supertest
2. Configure Jest
Add the following configuration in your package.json
:
"scripts": {
"test": "jest"
},
"jest": {
"testEnvironment": "node"
}
3. Create Tests
Create a folder named tests
in the root directory. Inside this folder, create test files for different parts of your application. Here’s an example for user authentication tests.
tests/user.test.js
const request = require('supertest');
const app = require('../server'); // Assuming your Express app is exported from server.js
const mongoose = require('mongoose');
const User = require('../models/userModel');
beforeAll(async () => {
await mongoose.connect(process.env.MONGO_URI, {
useNewUrlParser: true,
useUnifiedTopology: true
});
});
afterAll(async () => {
await mongoose.connection.close();
});
describe('User Authentication', () => {
it('should register a new user', async () => {
const res = await request(app)
.post('/api/users/register')
.send({
name: 'Test User',
email: 'testuser@example.com',
password: 'password',
role: 'customer'
});
expect(res.statusCode).toEqual(201);
expect(res.body).toHaveProperty('token');
});
it('should login an existing user', async () => {
const res = await request(app)
.post('/api/users/login')
.send({
email: 'testuser@example.com',
password: 'password'
});
expect(res.statusCode).toEqual(200);
expect(res.body).toHaveProperty('token');
});
it('should not login with incorrect password', async () => {
const res = await request(app)
.post('/api/users/login')
.send({
email: 'testuser@example.com',
password: 'wrongpassword'
});
expect(res.statusCode).toEqual(401);
});
it('should not register a user with existing email', async () => {
const res = await request(app)
.post('/api/users/register')
.send({
name: 'Test User',
email: 'testuser@example.com',
password: 'password',
role: 'customer'
});
expect(res.statusCode).toEqual(400);
});
});
4. Run Tests
Run your tests using the following command:
npm test
Step 2: Deployment
We will deploy our application to Heroku, a cloud platform that supports Node.js applications.
1. Install Heroku CLI
Follow the instructions to install the Heroku CLI from the Heroku Dev Center.
2. Log in to Heroku
Log in to your Heroku account using the Heroku CLI:
heroku login
3. Create a Heroku Application
Create a new Heroku application:
heroku create my-crm-backend
4. Add a MongoDB Add-on
Add MongoDB to your Heroku application using the mLab add-on (replace my-crm-backend
with your app name):
heroku addons:create mongolab:sandbox --app my-crm-backend
5. Set Environment Variables
Set the necessary environment variables for your application:
heroku config:set JWT_SECRET=your_jwt_secret --app my-crm-backend
6. Deploy Your Application
Initialize a git repository (if not already done) and push your code to Heroku:
git init
heroku git:remote -a my-crm-backend
git add .
git commit -m "Initial commit"
git push heroku master
7. Open Your Application
Open your deployed application in a web browser:
Congratulations! You’ve now built a fully functional CRM backend with Node.js and Express, complete with user authentication, data models, controllers, advanced filtering and pagination, and testing. You’ve also successfully deployed the application to Heroku. This comprehensive approach ensures that your CRM system is robust, scalable, 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.