How to get only required fileds from Mongoose Schema? - node.js

I'm looking for a way to send a custom error message to the client when a required input is not set.
A sample Mongoose Schema:
this._schema = new mongoose.Schema({
email: {
type: String,
unique: true,
required: true
}
});
I use a this._schema.pre('validate') function to test if a required input is missing and, if it is missing, pass a custom AppError to my next() function which will handle the rest.
The pre-validation:
this._schema.pre('validate', function(next) {
if(!this.email) return next(new AppError('Email not set'));
next();
});
I don't want to check for each field manually using a list of if statements. Rather I would like to loop over all required fields and throw the error if one of them is missing (loop should be recursive too for nested fields).
Question:
How can I loop over required fields and pass the first missing field to an Error?
Note: I'm not sure if this is the right way, so other interpretations are welcome! Found something here, but I'm not sure that's the correct answer.

Related

Message from Yup custom validator returns undefined for the names of path and reference

I am trying to write a custom test for the yup validation library for use in a node/express app that tests whether two fields are the same - use case e.g. testing whether password and confirm password fields match. The logic is working, however the message provided from the method is not.
Custom validator code modified from: https://github.com/jquense/yup/issues/97#issuecomment-306547261
Custom validator
yup.addMethod(yup.string, 'isMatch', function (ref, msg) {
return this.test({
name: 'isMatch',
message: msg || `${this.path} must be equal to ${this.reference}`,
params: {
reference: ref.path
},
test: function (value) {
return value === this.resolve(ref);
}
});
});
Example use
const schema = yup.object().shape({
password: yup.string().min(8),
passwordConfirm: yup.string().isMatch(yup.ref('password'))
})
const payload = {
password: 'correctPassword',
passwordConfirm: 'incorrectPassword'
}
schema.validate(payload)
The above method works as expected from a logic perspective. However, in the error message returned, the value of this.path and this.reference are both undefined (i.e. undefined must be equal to undefined). It should read passwordConfirm must be equal to password.
I had to add this. in front of path and reference, otherwise node crashes with a ReferenceError that path/reference is not defined.
You do not need to write a custom validation function to check if two fields match, you can use the built in validator.oneOf
Whitelist a set of values. Values added are automatically removed
from any blacklist if they are in it. The ${values} interpolation can
be used in the message argument.
Note that undefined does not fail this validator, even when undefined
is not included in arrayOfValues. If you don't want undefined to be a
valid value, you can use mixed.required.
See here ref: oneOf(arrayOfValues: Array, message?: string | function): Schema Alias: equals
So you could re-write your schema as follows (I've added required also to the password fields):
const schema = yup.object().shape({
password: yup.string().min(8).required('Required!'),
passwordConfirm: yup.string().oneOf([Yup.ref('password')], 'Password must be the same!').required('Required!')
})

How do I generate a new Error() with Mongoose that maintains the same nested structure as the standard Validation Error object?

Question
How do I generate an error that is more easily iterable on the front-end from Mongoose, that maintains the standard Validation error structure using new Error()?
Issue
I have a handful of validations that run on my Mongoose schema. I have a pre-save function that I'm using to encrypt a password using bcrypt. I'm trying to make sure I catch all promises, and I've got a situation where I may receive an error from bcrypt if a user's password is not successfully hashed. I'd like to generate a generic error that I can have display on the front-end. Right now I iterate through validation errors in the error.errors object which is returned from Mongoose in the following format:
// Schema
const MySchema = new Schema(
{
myField: {
type: String,
minlength: [3, "My Field must be 3-30 characters."],
maxlength: [30, "My Field must be 3-30 characters."],
required: [true, "My Field is required."],
trim: true
},
)
// Errors returned in the following format:
errors: {
myField: {
message: "Custom error warns foo must be bar."
},
// ... more error'd fields here
}
I'd like to add a custom error in the same format, so that the single error.errors object I'm sending back to my front-end in JSON format is easily iterable. Right now, I take my bcrypt error, and format my own object in this similar format, but it's not a very elegant way to do things.
I may be confused on how to properly use New Error("My custom error here") and may be making my validation harder than it needs to be, and would love any insight.
Code
You can see on Line 88 in my User-model.js that I'm catching a bcrypt error and formatting an object that resembles the same hierarchy as the standard validation errors object (afore noted).
I then send the error.errors object via JSON from the backend Line 19 in user-controller.js to the front-end for iteration.
Is there a cleaner way to utilize New Error() and make my error handling a bit nicer, without having to format my own error objects in this way?

Purpose of 'isUnique' field in Mongoose

I don't understand the purpose of fields like unique and required in Mongoose schemas. In the case of unique, it appears that you have to write your own methods to query MongoDB to see if the value already exists. I suppose with 'required' Mongoose just needs to check if that value was passed in the constructor of a Mongoose model. But with unique, I don't understand the purpose of that.
userSchema = mongoose.Schema({
username: {
type: String,
unique: true,
required: true,
validate: [validation.usernameValidator, 'not a valid username']
},
...
in order to validate username, I have to create my own function = validation.usernameValidator to check MongoDB to see if the username already exists. So then what is the point of isUnique?
unique will create a MongoDB "unique" index on the property, preventing documents with the same value for that property to exist in the same collection.
However, since it's enforced by the database, the only way this works is by saving a document and catching the duplicate-key-error that MongoDB will return.
You can check for that error like so:
if (err && err.code === 11000) { ...duplicate... }

Validation errors in custom instance or static methods in a Mongoose model

I have a basic Mongoose model with a Meeting and Participants array:
var MeetingSchema = new Schema({
description: {
type: String
},
maxNumberOfParticipants: {
type: Number
},
participants: [ {
type: Schema.ObjectId,
ref: 'User'
} ]
});
Let's say I want to validate that the number of participants added doesn't exceed the maxNumberOfParticipants for that meeting.
I've thought through a few options:
Custom Validator - which I can't do because I have to validate one attribute (participants length) against another (maxNumberOfParticipants).
Middleware - i.e., pre-save. I can't do this either because my addition of participants occurs via a findOneAndUpdate (and these don't get called unless I use save).
Add validation as part of my addParticipants method. This seems reasonable, but I'm not sure how to pass back a validation error from the model.
Note that I don't want to implement the validation in the controller (express, MEAN.js stack) because I'd like to keep all logic and validations on the model.
Here is my addParticipants method:
MeetingSchema.methods.addParticipant = function addParticipant(params, callback) {
var Meeting = mongoose.model('Meeting');
if (this.participants.length == this.maxNumberOfParticipants) {
// since we already have the max length then don't add one more
return ????
}
return Meeting.findOneAndUpdate({ _id: this.id },
{ $addToSet: { participants: params.id } },
{new: true})
.populate('participants', 'displayName')
.exec(callback);
};
Not sure how to return a validation error in this case or even if this pattern is a recommended approach.
I wouldn't think that's it's common practice for this to be done at the mongoose schema level. Typically you will have something in between the function getting called and the database layer (your schema) that performs some kind of validation (such as checking max count). You would want your database layer to be in charge of just doing simple/basic data manipulation that way you don't have to worry about any extra dependencies when/if anything else calls it. This may mean you'd need to go with route 1 that you suggested, yes you would need to perform a database request to find out what your current number of participants but I think it the long run it will help you :)

Password confirmation and external Model validation in Sails.js

I've been playing around with Sails for maybe one day. I'm trying to wrap my head around what would be the best way to do extensive validation in Sails.js.
Here is the scenario:
Registration Form:
Username: _______________
E-Mail: _______________
Password: _______________
Confirm: _______________
User inputs:
a correct e-mail
a username that already exists
two passwords that don't match
Desired outcome:
Username: _______________ x Already taken
E-Mail: _______________ ✓
Password: _______________ ✓
Confirm: _______________ x Does not match
Requirements, a few key points:
The user receives all error messages (not just the first one) for every aspect of his input. They are not vague ("username already taken" or "username must be at least 4 letters long" is better than "invalid username")
The built-in model validation can obviously not be responsible for checking a matched password confirmation (SRP)
What I think I need to do:
UserController:
create: function(req, res) {
try {
// use a UserManager-Service to keep the controller nice and thin
UserManager.create(req.params.all(), function(user) {
res.send(user.toJSON());
});
}
catch (e) {
res.send(e);
}
}
UserManager:
create: function(input, cb) {
UserValidator.validate(input); // this can throw a ValidationException which will then be handled by the controller
User.create(input, cb); // this line should only be reached if the UserValidator did not throw an exception
}
User: (model)
attributes: {
username: {
type: 'string',
required: true,
minLength: 3,
unique: true
},
email: {
type: 'email',
required: true,
unique: true
},
password: {
type: 'string',
required: true
}
}
UserValidator:
This is the tricky part. I need to combine input-specific validation (does the password confirmation match?) with the Model validation (is the username taken and is the e-mail address valid?).
If there was a way to instantiate a User-model and perform validation without saving to the database in Sails/Waterline I think this would be quite straight-forward, but there doesn't seem to be that option.
How would you go about solving this problem? Thank you very much for your help!
You can do this in your model:
module.exports = {
types: {
mycustomtype: function (password) {
return password === this.confirm;
}
},
attributes: {,
password:{
type: 'STRING',
required: true,
mycustomtype: true
}
}
}
There are going to be some validations that you can perform immediately on the client-side without needing to round-trip to the server. Things like comparing the password with the confirmation password, as well as verifying a string matches an email regex can be done with client-side javascript.
For other things like checking whether a username exists or not, you could use an ajax call to sails to directly ask it 'does this username exist' and provide real-time validation on the client-side based on the result, or you can wait until the user submits the form and parse the form submission to display those validations. Since checking ahead of time for things like this aren't 100% reliable (i.e. someone could create a user with that name after the check but prior to the form being posted back), some people choose to forgo the pre-check and only handle the error after post.
Waterline has its own built-in validation mechanism called Anchor, which is built on validator.js (previously called node-validator). For a full list of validations available, see here. I would recommend that instead of defining a separate validation layer, you define a method that parses the sails validation messages and formats them in a way that is user-friendly and consistent.
If you want to perform your own validations outside of what Waterline would do for you, you could do those validations inside a lifecycle callback, for instance the beforeCreate(values, callback) lifecycle callback. If you detect errors, you could pass them into the callback as the first parameter, and they would be passed back as an error to the caller of the create collection method.
An alternative to using a lifecycle callback, would be to create your own collection method that handles the create. Something like this:
Users.validateAndCreate(req.params.all(), function (err, user) {
...
});
More information about how to create a collection method like this can be found in my answer to this question: How can I write sails function on to use in Controller?

Resources