Get Mongoose validation error message in React - node.js

I am trying to validate user creation/editing etc with Mongoose and get back the message on my front end, but all I get is
POST http://192.168.0.11:3000/users/create 400 (Bad Request)
CreateUser.jsx:48 Error: Request failed with status code 400
at e.exports (createError.js:16)
at e.exports (settle.js:17)
at XMLHttpRequest.d.onreadystatechange (xhr.js:61)
My User schema:
const User = new mongoose.Schema({
Name: {
type: String,
required: "Username is required for a user!",
minlength: 4,
maxlength: 16,
},
Password: {
type: String,
required: "Password is required for a user!",
minlength: 4,
maxlength: 20,
},
Role: {
type: String,
required: "User must have a role!",
enum: ["Operator", "Admin"],
}
});
In Node:
router.post("/create", async (req, res) => {
try {
const user = new User({
Name: req.body.Name,
Password: req.body.Password,
Robots: req.body.Robots,
Role: req.body.Role,
});
await user.save();
res.send("success");
} catch (e) {
console.log(e);
res.status(400).json("Error" + e);
}
});
And in React:
try {
const userCreated = await axios.post(`${ENDPOINT}/users/create`, user);
console.log(userCreated);
} catch (e) {
console.log(e);
}
If it is successful I get back the "success" message, but otherwise I keep getting the POST 400 bad request message.
If I console.log it within node it does throw validation failed errors, but I can't get the error back on the front end.

I tried almost the same example with one of my express boilerplate repo here and I was able to return Mongo validation error like this.
part of an User model
first_name: {
type: String,
trim: true,
minlength: 4,
}
controller
try {
const user = await new User(req.body);
const newUser = await user.save();
res.status(201).json({ status: true, newUser });
} catch (error) {
console.log(error);
res.status(400).json(error);
}
Error response I got with 400 Bad Request, so you can check if name == 'ValidationError' in catch of your react app and can also use errors to display with the field.
{
"errors": {
"first_name": {
"message": "Path `first_name` (`a`) is shorter than the minimum allowed length (4).",
"name": "ValidatorError",
"properties": {
"message": "Path `first_name` (`a`) is shorter than the minimum allowed length (4).",
"type": "minlength",
"minlength": 4,
"path": "first_name",
"value": "a"
},
"kind": "minlength",
"path": "first_name",
"value": "a"
}
},
"_message": "User validation failed",
"message": "User validation failed: first_name: Path `first_name` (`a`) is shorter than the minimum allowed length (4).",
"name": "ValidationError"
}

Related

AJV validation doesn't return multiple errors when different values are missing in the fastify request body

I have a fastify server with a post endpoint. It accepts a JSON request body with a validation. Server code below:
const fastify = require("fastify");
const server = fastify({
ajv: {
customOptions: {
allErrors: true,
},
},
logger: true,
});
const schema = {
schema: {
body: {
type: "object",
properties: {
data: {
type: "array",
items: {
type: "object",
properties: {
foo: {
type: "string",
},
bar: {
type: "string",
},
},
required: ["foo", "bar"],
},
},
},
required: ["data"],
},
},
};
server.post("/", schema, function (request, reply) {
console.log({
request: {
body: JSON.stringify(request.body),
},
});
reply.send({ message: "hello" });
});
server.listen(3000, function (err, address) {
if (err) {
fastify.log.error(err);
process.exit(1);
}
console.log(`server listening on ${address}`);
});
Below is a valid request body.
{ "data":[{ "bar": "bar exists", "foo": "foo exists" }]}
When I try to access the same server with multiple values in input missing i.e.,
{ "data":[{ "bar": "bar exists, foo missing" }, {}] }
I am getting the below response.
{
"statusCode": 400,
"error": "Bad Request",
"message": "body.data[0] should have required property 'foo', body.data[1] should have required property 'foo', body.data[1] should have required property 'bar'"
}
I want to get each error separately, instead of getting a single large error message as this request can go very large. I have tried a bit of trial around the ajv options but couldn't find anything.
Any help is appreciated. Cheers :)
You need to have a custom parser after the error is caught.
In order to achieve this approach, there is a method called setErrorHandler.
According to Fastify documentation:
Set a function that will be called whenever an error happens.
This is a simple parser, but you may need to change it to your taste:
server.setErrorHandler(function (error, request, reply) {
if (error.validation) {
reply.status(400).send({
statusCode: error.statusCode,
error: 'Bad Request',
message: error.validation.map(err => `${err.instancePath} ${err.message}`)
});
}
})
// rest of code

Mongoose update validation breaks the update request

price: {
type: Number,
required: [true, 'A tour must have a price'],
},
priceDiscount: {
type: Number,
validate: function (val) {
return val < this.price;
}
The validation here tests if the discount price is less than the actual price if so it should work with no problems (it works if I am creating a new tour on the update it doesn't)
It just gives back a validation error even if the discount is less than the price ( "price": 997,
"priceDiscount":10)
"status": "FAIL",
"message": {
"errors": {
"priceDiscount": {
"name": "ValidatorError",
"message": "Validator failed for path `priceDiscount` with value `10`",
"properties": {
"message": "Validator failed for path `priceDiscount` with value `10`",
"type": "user defined",
"path": "priceDiscount",
"value": 10
},
"kind": "user defined",
"path": "priceDiscount",
"value": 10
}
},
"_message": "Validation failed",
"name": "ValidationError",
"message": "Validation failed: priceDiscount: Validator failed for path `priceDiscount` with value `10`"
}
I already have my runValidators: true
exports.UpdateTour = async (req, res) => {
try {
const upTour = await Tour.findByIdAndUpdate(req.params.id, req.body, {
new: true,
runValidators: true,
});
res.status(200).json({
status: 'success',
data: {
tour: `UPDATE TOUR #${req.params.id}
${upTour}`,
},
});
} catch (err) {
res.status(400).json({
status: 'FAIL',
message: err,
});
}
};
I found a solution that might not be optimal but it worked. I used a middleware function that checks the condition whenever the findByIdAndUpdate is triggered and it solved the problem.
Mongoose middleware docs where I came up with this idea
code sample:
tourSchema.post(/Update$/, async function (docs, next) {
let tour = await this.model.findOne(this.getQuery());
if (tour.price <= tour.priceDiscount) {
//set the discount price to 0 if the price is lower
tour.set({ priceDiscount: 0 });
tour.save();
next(new Error('The discount price is larger than the price'));
} else {
next();
}
});

MERN :: Mongoose Validation Errors :: How to Display Errors in React

I have set up Mongoose custom validation with errors and would like to display these error messages in React. I am unfortunately unable to retrieve the error messages. I have tried looking for solutions, but am unfortunately still having trouble.
My code is as follows:
Server-side:
- dataModel.js
const mongoose = require("mongoose");
const uniqueValidator = require("mongoose-unique-validator");
const moment = require("moment");
const dataSchema = mongoose.Schema(
{
name: {
type: String,
required: [true, "Name is required."],
validate: {
validator: function (name) {
return /^[a-zA-Z]+$/.test(name);
},
message: "Only alphabetic characters allowed.",
},
},
surname: {
type: String,
required: [true, "Surname is required."],
validate: {
validator: function (surname) {
return /^[a-zA-Z]+$/.test(surname);
},
message: "Only alphabetic characters allowed.",
},
},
idNumber: {
type: String,
required: [true, "ID Number is required."],
unique: true,
validate: [
{
validator: function (idNumber) {
return idNumber.toString().length === 13;
},
message: (idNumber) =>
`ID Number Must Have 13 Numbers. You entered ${
idNumber.value
}, which is ${idNumber.value.toString().length} numbers long.`,
},
{
validator: function (idNumber) {
return !isNaN(parseFloat(idNumber)) && isFinite(idNumber);
},
message: (idNumber) =>
`ID Number Can Only Contain Number Values. You entered ${idNumber.value}.`,
},
],
},
dateOfBirth: {
type: String,
required: [true, "Date of Birth is required."],
validate: {
validator: function (dateOfBirth) {
return moment(dateOfBirth, "DD/MM/YYYY", true).isValid();
},
message: "Invalid Date of Birth Format. Expected DD/MM/YYYY.",
},
},
},
{
timestamps: true,
}
);
dataSchema.plugin(uniqueValidator, { message: "ID Number Already Exists." });
module.exports = mongoose.model("Data", dataSchema);
- dataController.js
exports.addController = async (req, res) => {
const { firstName, surname, idNumber, dateOfBirth } = req.body;
const newData = new Data({
name: firstName,
surname,
idNumber,
dateOfBirth,
});
try {
await newData.save();
res.send({ message: "Data Added Successfully" });
} catch (error) {
if (error.name === "ValidationError") {
let errors = {};
Object.keys(error.errors).forEach((key) => {
errors[key] = error.errors[key].message;
});
console.log(errors)
return res.status(400).send(errors);
}
res.status(500).send("Something went wrong");
}
};
Output - console.log:
Client-side:
- dataForm.js
const addData = async () => {
try {
axios({
url: "/data/add",
method: "post",
data: {
firstName,
surname,
idNumber,
dateOfBirth,
},
headers: {
"Content-type": "application/json",
},
}).then(function (response) {
alert(response.data.message);
console.log(response.data.message);
});
} catch (error) {
console.log(error);
}
};
Output - Console:
Output - Postman (Initial):
{
"message": [
"Only alphabetic characters allowed.",
"ID Number Can Only Contain Number Values. You entered 888888888888a."
],
"error": {
"errors": {
"surname": {
"name": "ValidatorError",
"message": "Only alphabetic characters allowed.",
"properties": {
"message": "Only alphabetic characters allowed.",
"type": "user defined",
"path": "surname",
"value": "Bösiger"
},
"kind": "user defined",
"path": "surname",
"value": "Bösiger"
},
"idNumber": {
"name": "ValidatorError",
"message": "ID Number Can Only Contain Number Values. You entered 888888888888a.",
"properties": {
"message": "ID Number Can Only Contain Number Values. You entered 888888888888a.",
"type": "user defined",
"path": "idNumber",
"value": "888888888888a"
},
"kind": "user defined",
"path": "idNumber",
"value": "888888888888a"
}
},
"_message": "Data validation failed",
"name": "ValidationError",
"message": "Data validation failed: surname: Only alphabetic characters allowed., idNumber: ID Number Can Only Contain Number Values. You entered 888888888888a."
}
}
Output - Postman (Current):
I would appreciate any help that anyone is willing to offer.
I have managed to sort the problem out and return and display the Mongoose validation errors on the React frontend.
I amended the React post method as follows:
const addData = async () => {
try {
let response = await axios({
url: "http://localhost:8080/data/add",
method: "post",
data: {
firstName,
surname,
idNumber,
dateOfBirth,
},
headers: {
"Content-type": "application/json",
},
})
.then((response) => {
alert(response.data.message);
})
.then(() => {
window.location.reload();
});
alert(response.data.message);
} catch (error) {
alert(Object.values(error.response.data) + ".");
}
};
I had to format the method as the error code was not being reached and had to return and display the data using Object.values() as the responses were objects.
Thank you #cmgchess for pointing me in the right direction.

getting the error in mongose Cast to ObjectId failed for value \"{ _id: 'allchatroom' }\" (type Object) at path \"_id\" for model \"user\""

module.exports.allchatrooms = async(req, res) => {
try {
const details = await Chatroom.find({});
return res.status(200).send({
status: 200,
message: "all the chatroom",
data:details,
});
}
catch (error) {
return res.status(400).send({
status: 00,
err: error,
});
}
};
error in postman
{
"status": 400,
"success": false,
"err": {
"stringValue": "\"{ _id: 'allchatroom' }\"",
"valueType": "Object",
"kind": "ObjectId",
"value": {
"_id": "allchatroom"
},
"path": "_id",
"reason": {},
"name": "CastError",
"message": "Cast to ObjectId failed for value \"{ _id: 'allchatroom' }\" (type Object) at path \"_id\" for model \"user\""
}
}
model file
const mongoose = require('mongoose');
const Chatroom = new mongoose.Schema({
chatroom_name:{
type:String,
require:true
}
})
module.exports = mongoose.model('chat',Chatroom);
i am try to make the chatroom application for the practice because i am new in this field so try to make this project how can handle the real time data but when i hit the api to get all the chatrooms list then i got this error

NodeJS,Mongoose: Required field validation

I can add a new item to the database if I get a correctly formatted JSON file in the body where every required field contains something. If its false, right now I just return a JSON file like this:
{
"succes": false
}
But I also want to return an error message. I have already implemented the error string in the Model but I dont know how can I pull this out, if the catch block catches the error...
My add new item method:
exports.addBootcamp = async (req, res, next) => {
try {
const bootcamp = await Bootcamp.create(req.body);
if (!bootcamp) {
return res.status(404).json({ succes: false });
}
res.status(201).json({
succes: true,
data: bootcamp
});
} catch (err) {
return res.status(404).json({ succes: false });
}
};
The beggining part of my Model:
const BootcampShema = new mongoose.Schema({
name: {
type: String,
required: [true, 'Please add a name'], //first error message
unique: true,
trim: true,
maxlength: [50, 'Name cannot be more than 50 characters']
},
slug: String,
description: {
type: String,
required: [true, 'Please add a description'], //second error message
maxlength: [500, 'Description cannot be more than 500 characters']
},
//...etc
Of course these are in seperate js files but I can export them.
In this case we'll get a ValidationError from database which will be encapsulated in error object.
Modify your catch statement to below:
try {
// as it is
}
catch (err) {
return res.status(404).json({
succes: false,
message: err.message
});
}
Mongo db return the error object as below. From this structure you can extract whatever info you want and return that to user.
{
"errors": {
"name": {
"message": "Please add a name",
"name": "ValidatorError",
"properties": {
"message": "Please add a name",
"type": "required",
"path": "name"
},
"kind": "required",
"path": "name"
}
},
"_message": "Name validation failed",
"message": "Name validation failed: camera_name: Please add a name",
"name": "ValidationError"
}
Here Please add a name is the same text we entered in our model.

Resources