Reputation: 3383
I'm attempting to use Express and Mongoose to create a simple form which allows the user to add new items to a collection.
Mongoose schema:
var Item = new Schema({
name : {type : String, required : true},
price : {type : Number, required : true},
description : {type: String, required: true},
});
Express route:
var create = function(req, res) {
Item.create(req.body, function(err) {
if (err) return res.render('admin/items/new', {title: "New Item", errors: _.values(err.errors)});
res.redirect('/admin/items/index');
});
};
Simple enough so far. What I want to do is add appropriate validation to ensure the price
entered by the user is numeric and if not show an appropriate error.
With the code above, if the user types non-numeric characters into the price text field I get a CastError thrown.
So, I have tried adding the following validation to my schema:
Item.path('price').validate(function(v, fn) {
if (typeof v === 'number' || v === undefined) return fn(true);
return fn(false);
}, "must-be-numeric");
However, it seems that Mongoose attempts to cast the value before applying this validation, so it is not possible to return an appropriate validation error for this, you still just get a CastError. Moreover the CastError prevents any validation checks on other fields being run.
I would think that what I'm trying to do is fairly common (in Rails you'd just add validate :price, :numericality => true
to your model and that would be it).
Is there an obvious pattern I'm missing which facilitates this sort of type validation?
How does everyone else do this?
Things I've tried/thought of:
input
element to prevent erroneous user input, but given Firefox and IE don't support it yet it's a non-starter. Upvotes: 3
Views: 5340
Reputation: 526
There is another way to do this. You can use a custom setter and a custom validator:
var setNumberOrUndefined = function (val) {
// this prevents set undefined if the user did not
// enter any value
if (val == '')
return null
// Return undefined prevents CastError
// now a validator must validate if it's a number or not
var v = Number(val)
return (isNaN(v))? undefined : v
}
var isNumberOrEmpty = function (val) {
// This prevents return false if the user did not
// enter any value
if (val === null)
return true
else
return 'number' == typeof val
}
var Item = new Schema({
name : {type : String, required : true},
price : {
type : Number,
required : true,
set: setNumberOrUndefined,
validate: [
{validator: isNumberOrEmpty, msg: 'Price must be numeric!'},
]
},
description : {type: String, required: true},
});
Upvotes: 3
Reputation: 9636
I know this is a very old post and my answer might not be very helpful to you but still I am posting this for the sake of users who are going to use this.
I personally would recommend using node-validator after the user has submitted his data(i.e even before the data is submitted to the mongoose). That way you can validate the user input itself which is a much safe idea to follow. Secondly this creates a clear code as the mongoose just lets you handle database rather than all the custom validation you were looking for.
You can use express-validator which is a middleware using node-validator. Its a very nice way to handle validation.
Though documented I personally dont think that pre-save is for validation actually. Its mostly to use to create any referenced data or emit events for use in other parts of your code.
I hope you also find this useful.
Upvotes: 4
Reputation: 78
Mongoose middleware is your friend.
Item.pre('save' function(next) {
if (this.price) {
if (!myValidationFunction(this.price)) {
return next(new Error('Price must be yada yada yada'))
}
}
next()
})
The pre('save' ... events fire before the mongoose-level validation, so you have a lot more control of how errors are generated. Then you set up a generic error handling middleware in express that traps all uncaught errors and formats error messages however you want. Express 2.x had a .on('error'... event, but that was removed in Express 3.
At essence error trapping middleware does something like this
app.use(function(err, req, res, next) {
if (!(err instanceof Error)) next()
// First param was some kind of error
// format and send err.message however you like
// in dev mode err.stack is usually interesting
// this code is usually terminal, and does not call next()
})
Couple of tricks using in using mongoose middleware that are not well documented:
First, to fail a save, simply pass an Error object to next. The database will not be touched. Second, you can distinguish between inserts and updates by checking the this.isNew property.
One advantage of using a pattern like this is that most popular node libraries use it, and so you can consolidate your error handling with some confidence that no matter what goes wrong you'll get a chance to format something reasonable.
One last note, if you adopt this approach generally you might find yourself wanting to create your own custom Error objects in node. These work great, but are a little tricky to set up. Here's a good article: http://dustinsenos.com/articles/customErrorsInNode
Hope that helps!
Upvotes: 3