NodeJS,Mongoose: Required field validation - node.js

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.

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

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.

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

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

Best way to wrap mongoose validation error

We know that mongoose provides us an easy way to do validation. But suppose you are using express+mongoose to building a microservice; and some clients (could be web-app, mobile app etc.) needs to consume it.
Usually, I prefer to response JSON back with simple error code and message. In most cases, the clients who can create their own messages depending on which language they are showing to users.
By default, if we catch the error from mongoose, we can get JSON response such as:
JSON Response
{
"errors": {
"price": {
"message": "Path `price` (-1) is less than minimum allowed value (0).",
"name": "ValidatorError",
"properties": {
"min": 0,
"type": "min",
"message": "Path `{PATH}` ({VALUE}) is less than minimum allowed value (0).",
"path": "price",
"value": -1
},
"kind": "min",
"path": "price",
"value": -1,
"$isValidatorError": true
},
"code": {
"message": "Product with given code already exists",
"name": "ValidatorError",
"properties": {
"type": "user defined",
"message": "Product with given code already exists",
"path": "code",
"value": "p-1000"
},
"kind": "user defined",
"path": "code",
"value": "p-1000",
"$isValidatorError": true
}
},
"_message": "Product validation failed",
"message": "Product validation failed: price: Path `price` (-1) is less than minimum allowed value (0)., code: Product with given code already exists",
"name": "ValidationError"
}
Restful Api Controller
exports.createOne = async(function* list(req, res) {
try {
const product = new Product(req.body)
const newProduct = yield product.save()
res.json(newProduct)
} catch (err) {
res.status(400).json(err)
}
})
Model Product.js
const mongoose = require('mongoose')
const Schama = mongoose.Schema
const minlength = [5, 'The value of `{PATH}` (`{VALUE}`) is shorter than the minimum allowed length ({MINLENGTH}).'];
const ProductSchema = new Schama({
code: { type: String, required: true, minlength, index: true, unique: true, trim: true, lowercase: true },
name: { type: String, required: true, trim: true },
price: { type: Number, required: true, min: 0, max: 100000 },
categories: [String],
})
ProductSchema.path('code').validate(function uniqueEmail(code, fn) {
const Product = mongoose.model('Product')
// Check only when it is a new Product or when code field is modified
if (this.isNew || this.isModified('code')) {
Product.find({ code }).exec((err, products) => {
fn(!err && products.length === 0)
})
} else fn(true)
}, 'Product with given code already exists')
ProductSchema.statics = {
/**
* List products
*
* #param {Object} options
* #api private
*/
pageList: function pageList(conditions, index, size) {
const criteria = conditions || {}
const page = index || 0
const limit = size || 30
return this.find(criteria)
.populate('user', 'name username')
.sort({ createdAt: -1 })
.limit(limit)
.skip(limit * page)
.exec()
},
}
mongoose.model('Product', ProductSchema)
What I expect
I am trying to wrap the error message to make it simple to consumer.
It could be like:
{
"errors": [
{
"message": "Path `price` (-1) is less than minimum allowed value (0).",
"code": "100020"
},
{
"message": "Product with given code already exists",
"code": "100021"
}
],
"success": false
}
The code and the corresponding message will be maintained on api documents. The message is usualy useful for consumer to understand the code and consumer (such as web client) could create their own message such as French messages according to the code and show to end users.
How can I leverage mongoose's valiation to accomplish this? Maybe I could loop erros's properties and combine an code using ${path}-${kind}.
I know that in most case, Client side should do the validation before calling apis. But there must be some cases that errors have to be thrown by APIs.
Any idea for this?

Resources