I'm trying to return multiple custom error messages from my Joi validation schema. Here is the schema
const Joi = require("@hapi/joi");
const string = Joi.string();
const emailSchema = string.email();
const usernameSchema = string
.min(3)
.max(30)
.error(() => "Username must be between 3 and 30 characters");
const passwordSchema = string
.min(6)
.error(() => "Password must be at least 6 characters");
const confirmPasswordSchema = Joi.valid(Joi.ref("passwordSchema")).error(
() => "Passwords must match"
);
const localRegistrationSchema = Joi.object().keys({
email: emailSchema.required().error(() => "Email is required"),
username: usernameSchema.required().error(() => "Username is required"),
password: passwordSchema.required().error(() => "Password is required"),
confirmPassword: confirmPasswordSchema
});
and here is where I am using the schema
const { error } = localRegistrationSchema.validate(req.body, {
abortEarly: false
});
console.log(error);
if (error) throw Boom.boomify(error);
But I keep getting an TypeError: Cannot read property 'filter' of undefined which looks to be caused by
details.push({
message,
path: item.path.filter((v) => typeof v !== 'object'),
type: item.code,
context: item.local
});
which is part of Joi's error handling code
I do not get this error when I don't attach the .error() part but I cannot get more than one error to show if I use .error(new Error("custom error message")
I can't figure out what is going wrong and I haven't been able to get any other way of returning multiple custom error messages to work
I debugged your code and simply returning () => 'some error message'
does not work for your solution. We need to return a function. You got an error because your path
property on the custom error message was undefined
.
const schema = Joi.object({
prop: Joi.string()
.min(9)
.error(() => 'min error message')
.required()
.error(() => 'required error message');
});
const schema = Joi.object({
username: Joi.string()
.min(9)
.required()
.error((errors) => {
for (err of errors) {
switch (err.code) {
case ('string.min'): {
return simpleErrorMsgFunc("prop min error message", ["prop"])(); // invoke
}
case 'any.required': {
return simpleErrorMsgFunc("prop is required", ["prop"])(); // invoke
}
default: {
return simpleErrorMsgFunc("prop has error", ["prop"])(); // invoke
}
}
}
}),
});
The heart of my solution is the following function. It returns a function
which returns an custom error object
.:
function simpleErrorMsgFunc(message, path) {
return () => {
return {
toString: () => message,
message,
path,
}
};
}
const Joi = require("@hapi/joi");
function simpleErrorMsgFunc(message, path) {
return () => {
return {
toString: () => message,
message,
path,
}
};
}
const localRegistrationSchema = Joi.object().keys({
// email is simple, we only need 1 error message
email: Joi.string()
.email()
.required()
.error(simpleErrorMsgFunc("Email is required", ["email"])),
// username is advanced, we need 2 error message
username: Joi.string()
.min(3)
.max(30)
.required()
.error((errors) => {
for (err of errors) {
switch (err.code) {
case ('string.min' || 'string.max'): {
return simpleErrorMsgFunc("username must be between 3 and 30 characters", ["username"])(); // invoke
}
case 'any.required': {
return simpleErrorMsgFunc("username is required", ["username"])(); // invoke
}
default: {
return simpleErrorMsgFunc("username has error", ["username"])(); // invoke
}
}
}
}),
// password is advanced, we need 2 error message
password: Joi.string()
.min(6)
.required()
.error((errors) => {
for (err of errors) {
switch (err.code) {
case ('string.min'): {
return simpleErrorMsgFunc("Password must be at least 6 characters", ["password"])(); // invoke
}
case 'any.required': {
return simpleErrorMsgFunc("Password is required", ["password"])(); // invoke
}
default: {
return simpleErrorMsgFunc("password has error", ["password"])(); // invoke
}
}
}
}),
confirmPassword: Joi.valid(Joi.ref("password"))
.error(simpleErrorMsgFunc("Passwords must match", ['confirmPassword']))
});
const req = {
body: {
email: '[email protected]',
username: 'hee',
password: '45645656',
confirmPassword: '45645656_',
},
};
const { error } = localRegistrationSchema.validate(req.body, {
abortEarly: false
});
console.log(JSON.stringify(error, null, 2));
P.S. I noticed that your
confirmPassword
property is not required!
Seening that this is still getting a lot of views, I'd like to add that this is how I am currently handling multiple errors with Joi. I'm just stringing together all my validation/sanitization functions and then rather than using a switch statement, all of my custom messages go in at the end in .messages({}). The same works with yup if you're using that on the frontend. This makes it much cleaner and more concise than a switch statement and an error message helper function.
const string = Joi.string();
const passPattern =
"^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*\\W)[a-zA-Z0-9\\S]{8,}$";
export const signupLocalSchema = Joi.object({
email: string.email().trim().lowercase().required().messages({
"string.email": "Not a valid email address.",
"string.empty": "Email is required.",
}),
username: string.min(3).max(30).trim().lowercase().required().messages({
"string.min": "Username must be between 3 and 30 characters.",
"string.max": "Username must be between 3 and 30 characters.",
"string.empty": "Username is required.",
}),
password: string.pattern(new RegExp(passPattern)).messages({
"string.pattern.base":
"Password must be at least 8 characters and contain at least 1 lowercase, 1 uppercase, 1 number and 1 special character.",
}),
confirmPassword: Joi.valid(Joi.ref("password")).messages({
"any.only": "Passwords must match.",
}),
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With