I have a function in express js to update my project like below:
projectController.ts
export async function updateProjectStatus(req: Request, res: Response) {
try {
const projectCode = req.params.projectCode;
const { status } = req.body;
const updatedProject = await Project.findOneAndUpdate(
{ projectCode },
{ status },
{ new: true }
);
if (!updatedProject) {
return res.status(404).send('Project not found');
}
res.status(200).send(updatedProject);
} catch (error) {
let errorMessage = 'Failed to update project status';
if (error instanceof Error) {
errorMessage = error.message;
}
res.status(500).send({ message: errorMessage });
}
}
// PATCH: update project details
type ProjectKeys = keyof ProjectType;
const allowedUpdates: ProjectKeys[] = [
'name',
'budget',
'advance',
'clientName',
'clientPhone',
'clientEmail',
'clientAddress',
'clientDetails',
'endDate',
'demoLink',
'typeOfWeb',
'description',
];
export async function updateProjectDetails(req: Request, res: Response) {
const projectCode = req.params.projectCode;
const {
name,
budget,
advance,
clientName,
clientPhone,
clientEmail,
clientAddress,
clientDetails,
endDate,
demoLink,
typeOfWeb,
description,
} = req.body;
console.log(req.body);
console.log(projectCode);
// Extract valid updates from request body
const updates = Object.keys(req.body);
const isValidOperation = updates.every((update) => {
return allowedUpdates.includes(update as ProjectKeys);
});
if (!isValidOperation) {
return res.status(400).send({ error: 'Invalid updates!' });
}
try {
const project = await Project.findOne({ projectCode });
if (!project) {
return res.status(404).send({ error: 'Project not found' });
}
const updatedProject = await Project.findOneAndUpdate(
{ projectCode },
{
name,
budget,
advance,
clientName,
clientPhone,
clientEmail,
clientAddress,
clientDetails,
endDate,
demoLink,
typeOfWeb,
description,
},
{ new: true}
);
res.status(200).send(updatedProject);
} catch (error) {
res.status(500).send({
message:
error instanceof Error
? error.message
: 'Failed to update project details',
});
console.log(error);
}
}
here I get the updated project details then find the project and then update it. While updating it I use runValidators: true
to run my validator in my project model. I have some validator in my projectModel.ts
like below:
import mongoose from 'mongoose';
import { ProjectType } from '../types/projectDocumentType';
const projectSchema = new mongoose.Schema<ProjectType>(
{
projectCode: {
type: String,
required: true,
unique: true,
},
name: {
type: String,
required: true,
trim: true,
},
budget: {
type: Number,
required: true,
validate: {
validator: function (value: number) {
return value >= 0;
},
message: 'Budget must be a positive number',
},
},
advance: {
type: Number,
required: true,
validate: {
validator: function (value: number) {
return value >= 0 && value <= this.budget;
},
message:
'Advance must be a positive number and less than or equal to the budget',
},
},
due: {
type: Number,
validate: {
validator: function (value: number) {
return value >= 0 && value <= this.budget;
},
message:
'Due must be a positive number and less than or equal to the budget',
},
},
totalPaid: {
type: Number,
default: 0,
validate: {
validator: function (value: number) {
return value >= 0 && value <= this.budget;
},
message:
'Total Paid must be a positive number and less than or equal to the budget',
},
},
clientName: {
type: String,
required: true,
ref: 'Client',
},
clientPhone: {
type: String,
required: true,
},
clientEmail: {
type: String,
required: true,
},
clientAddress: {
type: String,
},
clientDetails: {
type: String,
},
startDate: {
type: String,
required: true,
default: new Date().toISOString().split('T')[0],
},
endDate: {
type: String,
required: true,
},
demoLink: {
type: String,
},
typeOfWeb: {
type: String,
},
description: {
type: String,
},
status: {
type: Boolean,
required: true,
default: false,
},
verifiedClientList: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Client',
},
],
projectManager: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true,
},
paymentList: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Payment',
},
],
},
{
timestamps: true,
}
);
projectSchema.pre('save', function (next) {
if (this.isNew) {
// Only calculate due when creating a new project
this.due = this.budget - this.advance;
}
next();
});
const Project = mongoose.model<ProjectType>('Project', projectSchema);
export default Project;
in projectMode.ts
in advance and due section I have validator function. It is working perfectly whenever I try to add new project. But when I try to update the project using postman it gives me validation error though my json is right. For example I send patch request for json as:
{
"name": "Project Alpha",
"budget": 10000,
"advance": 900,
"clientName": "Rezaul",
"clientPhone": "1234567890",
"clientEmail": "john@example.com",
"clientAddress": "123 Main St",
"clientDetails": "Long-term client",
"endDate": "2024-06-01",
"demoLink": "http://example.com/demo",
"typeOfWeb": "E-commerce",
"description": "A high-end e-commerce platform"
}
but when I send it it shows me Error: Validation failed: advance: Advance must be a positive number and less than or equal to the budget but here advance (900) < budget (10000). so why my validator is not working with runValidators: true
and how to fix that without using save()
I am expecting when I use
{
"name": "Project Alpha",
"budget": 10000,
"advance": 900,
"clientName": "Rezaul",
"clientPhone": "1234567890",
"clientEmail": "john@example.com",
"clientAddress": "123 Main St",
"clientDetails": "Long-term client",
"endDate": "2024-06-01",
"demoLink": "http://example.com/demo",
"typeOfWeb": "E-commerce",
"description": "A high-end e-commerce platform"
}
it will update the project successfully by checking validator.
I solve the problem by using Post Schema Definition Validation or Path-based validation using path().validate():
. for example, for advance
in projectModel.ts
I use:
projectSchema.path('advance').validate(function (value: number) {
console.log('this.budget', this.get('budget'));
return value >= 0 && value <= this.get('budget');
}, 'Advance must be a positive number and less than or equal to the budget');
here I get the updated budget value by using this.get('budget')
. This gives me the current budget value. As per mongoose official documentation,
When running in validate()
or validateSync()
, the validator can access the document using this
. When running with update validators, this
is the Query, not the document being updated! Queries have a get()
method that lets you get the updated value. Link
So I use this.get()
to get the updated value inside validation before update the document which I was passing in findOneAndUpdate()
method of mongoose.
const updatedProject = await Project.findOneAndUpdate(
{ projectCode },
{
name,
budget,
advance,
clientName,
clientPhone,
clientEmail,
clientAddress,
clientDetails,
endDate,
demoLink,
typeOfWeb,
description,
},
{ new: true, runValidators: true }
);
This validation is now working for both save()
method and findOneAndUpdate()
in mongoose.
Make sure you remove the schema-based validation if you use Path-based validation. I mean I also remove the validate and validator function in advance
section.
validate: {
validator: function (value: number) {
return value >= 0 && value <= this.budget;
},
message:
'Advance must be a positive number and less than or equal to the budget',
},
and also use runValidators: true
inside findOneAndUpdate()
as an option.