MERN :: Mongoose Validation Errors :: How to Display Errors in React - node.js

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.

Related

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();
}
});

Get Mongoose validation error message in React

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"
}

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.

How to post and populate bill

I've got a relational json called "client" inside Bill's model. This is my code:
const mongoose = require("mongoose");
const { Schema } = mongoose;
const billSchema = new Schema({
number: Number,
date: { type: Date, default: Date.now() },
type: String,
local: String,
client: { type: mongoose.Schema.Types.ObjectId, ref: "clients", required: true },
detail: [
{
quantity: Number,
product: { code: Number, name: String, price: Number },
undertotal: Number
}
],
total: Number
});
mongoose.model("bills", billSchema);
this is my post route:
app.post("/api/bills", async (req, res) => {
const { number, type, local, client, detail, total } = req.body;
await Client.findById(req.body.client._id).then(client => {
if (!client) {
return res.status(404).json({
message: "client not found"
});
}
});
const bill = new Bill({
number,
date: new Date(),
type,
local,
client,
detail,
total
});
try {
let newBill = await bill.save();
res.status(201).send(newBill);
} catch (err) {
if (err.name === "MongoError") {
res.status(409).send(err.message);
}
res.status(500).send(err);
}
});
//my get route
app.get("/api/bills", function(req, res) {
Bill.find({}, function(err, bills) {
Client.populate(bills, { path: "clients" }, function(err, bills) {
res.status(200).send(bills);
});
});
});
I want something like this:
{
"number": 302,
"type": "c",
"local": "porstmouth",
"client": {
"address": {
"street": "victoria street",
"number": 1001,
"floor": "2",
"flat": 4
},
"_id": "5dab929613fb682b48e4ca6b",
"name": "luke skywalker",
"mail": "l.skywalker#yahoo.com",
"cuil": "39193219",
"phone": 128391,
"__v": 0
},
"detail": [
{
"quantity": 500,
"product": {
"code": 300,
"name": "P2",
"price": 800
},
"undertotal": 5000
}
],
"total": 11000
}
But I see this result:
{
"date": "2019-10-20T12:27:17.162Z",
"_id": "5dac52a577e09b4acc45718d",
"number": 302,
"type": "c",
"local": "porstmouth ",
"client": "5dab929613fb682b48e4ca6b",
"detail": [
{
"_id": "5dac52a577e09b4acc45718e",
"quantity": 500,
"product": {
"code": 300,
"name": "P2",
"price": 800
},
"undertotal": 5000
}
],
"total": 11000,
"__v": 0
}
I don't want to see id client only. I want to see all content from client inside bill.
I tried to do with populate method, but I haven't results.
So, Which is form to post and populate a nested json relational object in this case?
While posting only clientId is enough.
So your post route can be like this (you both used await and then, which is incorrect, so I refactored it to use only await)
app.post('/api/bills', async (req, res) => {
const { number, type, local, client, detail, total } = req.body;
let existingClient = await Client.findById(req.body.client._id)
if (!existingClient) {
return res.status(404).json({
message: "client not found"
});
}
const bill = new Bill({
number,
date: new Date(),
type,
local,
client: req.body.client._id
detail,
total
})
try {
let newBill = await bill.save();
res.status(201).send(newBill);
} catch (err) {
if (err.name === 'MongoError') {
res.status(409).send(err.message);
}
res.status(500).send(err);
}
});
And in the get route to retrieve all the client info you need to populate it like this:
app.get('/api/bills', async (req, res) => {
try {
const bills = await Bill.find({}).populate("clients");
res.status(200).send(bills);
} catch (err) {
console.log(err);
res.status(500).send(err);
}
}
)

Problem with ottoman not resolving the references

I have two models in my ottoman 1.0.5 setup. One holds contact info which includes an emails array of docs and then the email doc. I can insert new contacts fine as well as emails in docs and the corresponding link in the contact doc for the new email.
Here is my model
const ottoman = require("ottoman")
ottoman.bucket = require("../app").bucket
var ContactModel = ottoman.model("Contact",{
timestamp: {
type: "Date",
default: function() {return new Date()}
},
first_name : "string",
last_name : "string",
emails: [
{
ref:"Email"
}
]} )
var EmailModel = ottoman.model("Email",{
timestamp: {
type: "Date",
default: function() {return new Date()}
},
type : "string",
address : "string",
name: "string"
} )
module.exports = {
ContactModel : ContactModel,
EmailModel : EmailModel
}
Now to get an contact and all its emails i use this function
app.get("/contacts/:id", function(req, res){
model.ContactModel.getById(req.params.id,{load: ["emails"]}, function(error, contact){
if(error) {
res.status(400).json({ Success: false , Error: error, Message: ""})
}
res.status(200).json({ Success: true , Error: "", Message: "", Data : contact})
})
})
Which returns me this
{
"Success": true,
"Error": "",
"Message": "",
"Data": {
"timestamp": "2019-01-30T23:59:59.188Z",
"emails": [
{
"$ref": "Email",
"$id": "3ec07ba0-aaec-4fd4-a207-c4272cef8d66"
}
],
"_id": "0112f774-4b5d-4b73-b784-60fa9fa2f9ff",
"first_name": "Test",
"last_name": "User"
}
}
if i go and log the contact to my console i get this
OttomanModel(`Contact`, loaded, key:Contact|0112f774-4b5d-4b73-b784-60fa9fa2f9ff, {
timestamp: 2019-01-30T23:59:59.188Z,
emails: [ OttomanModel(`Email`, loaded, key:Email|3ec07ba0-aaec-4fd4-a207-c4272cef8d66, {
timestamp: 2019-01-31T00:36:01.264Z,
_id: '3ec07ba0-aaec-4fd4-a207-c4272cef8d66',
type: 'work',
address: 'test#outlook.com',
name: 'Test Outlook',
}),
OttomanModel(`Email`, loaded, key:Email|93848b71-7696-4ef5-979d-05c19be9d593, {
timestamp: 2019-01-31T04:12:40.603Z,
_id: '93848b71-7696-4ef5-979d-05c19be9d593',
type: 'work',
address: 'newTest#outlook.com',
name: 'Test2 Outlook',
}) ],
_id: '0112f774-4b5d-4b73-b784-60fa9fa2f9ff',
first_name: 'Test',
last_name: 'User',
})
This shows that emails was resolved but why does it not show up in the returned json. On the other hand if i return contact.emails i get the resolved emails just fine. So i hope someone can shed some light on what i am missing here
I asked a similar question on the couchbase forum, and I also found out the solution:
(a slight difference that the result of my search is an array not an object like in your case)
forum.couchbase.com
app.get("/assets", (req, res) => {
AssetModel.find({}, { load: ["assetModelId", "assetGroupId", "assetTypeId"] }, (err, results) => {
if (err) return res.status(400).send("no asset found");
const assets = [];
results.map(asset => {
assets.push({...asset});
});
res.status(200).send(assets)
});
});

Resources