javascriptnode.jsvue.jsjoiionic-vue

Unable to throw custom error messages while using Joi package in frontend


This is my first attempt in trying to use joi in frontend and the issue i am facing is that i am not getting custom errors when i try with bad data.

Here is my code in the validation.js

import Joi from "joi";

export const loginSchema = Joi.object({
  email: Joi.string()
    .pattern(new RegExp("^[^@\\s]+@[domainName]\\.(com|in)$"))
    .email({
      minDomainSegments: 2,
      tlds: { allow: ["com", "in"] },
    })
    .required()
    .messages({
      "string.email": "Email must be a valid email",
      "string.pattern.base":
        "Email domain must be [domainName] and end with .com or .in",
      "any.required": "Email is a required field",
    }),
  password: Joi.string()
    .pattern(new RegExp("^[a-zA-Z0-9]{3,30}$"))
    .required()
    .messages({
      "string.base": "Password should be a type of text",
      "string.empty": "Password cannot be an empty field",
      "string.pattern.base":
        "Password should have a minimum length of 3 and a maximum length of 30, and contain only letters and numbers",
      "any.required": "Password is a required field",
    }),
});


For the frontend I am using Ionic Vue version 8 and here is the crucial code from that file as it is Too long;

                  <!-- Email -->
                  <ion-item>
                    <ion-input
                      placeholder="Email Address"
                      label-placement="stacked"
                      color="light"
                      type="email"
                      v-model.trim="form.email"
                      @ionBlur="validateField('email')"
                      :error-text="errors.email"
                    >
                      <ion-icon
                        slot="start"
                        :icon="mailOutline"
                        aria-hidden="true"
                      ></ion-icon>
                    </ion-input>
                  </ion-item>

                  <!-- Password -->
                  <ion-item>
                    <ion-input
                      placeholder="Password"
                      label-placement="stacked"
                      color="light"
                      type="password"
                      v-model.trim="form.password"
                      @ionBlur="validateField('password')"
                      :error-text="errors.password"
                    >
                      <ion-icon
                        slot="start"
                        :icon="keyOutline"
                        aria-hidden="true"
                      ></ion-icon>
                      <ion-input-password-toggle
                        slot="end"
                        color="dark"
                      ></ion-input-password-toggle>
                    </ion-input>
                  </ion-item>
    <script setup>
    import { loginSchema } from "../../services/Validators";
    import { reactive } from "vue";
const form = reactive({
  email: "",
  password: "",
});
const errors = reactive({
  email: null,
  password: null,
});

const validateField = (field) => {
  const data = { [field]: form[field] };

  console.log("Data: ", { ...data });
  console.log("datatype: ", typeof data[field]);

  const schema = loginSchema.extract(field);
  const { error } = schema.validate(
    { email: data[field] },
    { abortEarly: false }
  );

  console.log(`Validation error for ${field}:`, { ...error });

  errors[field] = error ? error.details[0].message : null;
};

</script>

Can someone Guide me as to what is wrong here cause when i log the error with some bad email here is the log that I get

{
    "_original": {
        "email": "dsfasdf@sdf.asd"
    },
    "details": [
        {
            "message": "\"value\" must be a string",
            "path": [],
            "type": "string.base",
            "context": {
                "label": "value",
                "value": {
                    "email": "dsfasdf@sdf.asd"
                }
            }
        }
    ]
}

ideally what should be logged is:

"string.pattern.base": "Email domain must be [domainName] and end with .com or .in",

Initially, i though I might not be sending the right data format or datatype format however when i printed data and dataType of the field this is what i see

data

{
    "email": "sdfsd@asdf.ds"
}

dataType

datatype:  string

Validation error for email:

{
    "_original": {
        "email": "sdfsd@asdf.ds"
    },
    "details": [
        {
            "message": "\"value\" must be a string",
            "path": [],
            "type": "string.base",
            "context": {
                "label": "value",
                "value": {
                    "email": "sdfsd@asdf.ds"
                }
            }
        }
    ]
}

since i am using Joi.object() where the keys match the schema keys and the values are the data to be validated i ruled out that it was due to incorrect data type or value.

When i test this schema with online tools/ sandboxes it works perfectly fine, however it not when i run it in the project. i am sure i am overlooking something, Please help me find that out


Solution

  • You cannot pass object when schema is extracted, so just pass the email string it will work

     let email = data[field];
     const { error } = schema.validate(
        email,
        { abortEarly: false }
     );
    

    And some recommendation, you won't need required method in each property if all the properties in a object are required.

    Instead use prefs method like below

    const loginSchema = Joi.object({
        email: Joi.string()
            .pattern(new RegExp('^[^@\\s]+@[domainName]\\.(com|in)$'))
            .email({
                minDomainSegments: 2,
                tlds: { allow: ['com', 'in'] }
            })
            .messages({
                'string.email': 'Email must be a valid email',
                'string.pattern.base': 'Email domain must be [domainName] and end with .com or .in',
                'any.required': 'Email is a required field'
            }),
        password: Joi.string().pattern(new RegExp('^[a-zA-Z0-9]{3,30}$')).messages({
            'string.base': 'Password should be a type of text',
            'string.empty': 'Password cannot be an empty field',
            'string.pattern.base':
                'Password should have a minimum length of 3 and a maximum length of 30, and contain only letters and numbers',
            'any.required': 'Password is a required field'
        })
    }).prefs({ presence: 'required' }); // Here 
    

    presence: 'required' tells that all the properties are required

    Joi Doc