Validation; joi brings you "Joy" 😁

Ever tried to do some sort of schema validation while building API(s) and you have to write a lot of If else conditions/statements to ensure you throw error when the user inputs wrong data? Yeah, it's stressful and trust me, you still won't cater for all scenarios.
The good news is joi has come to your rescue and it's here to bring you joy [pun intended].
In this tutorial, I will be showing you how to validate with joi.
joi helps define data easily without the need to worry about not throwing error; joi does the job for you by throwing error.

[For this tutorial, I assumed you already know how to setup a server with express.js]

Install all dependencies required for this task npm install joi express

In this tutorial we'll be validating the following details;

  • username
  • Password
  • firstname
  • lastname
  • email
  • phonenumber
  • date of birth [DOB]
  • Sex

Let's get right into it πŸ’»πŸ’»πŸ’»

const Joi = require("joi"); 
app.post("/register", async (req, res) => {

  try {

    // Define Schema

    const schema = Joi.object({
      username: Joi.string().min(6).alphanum().uppercase().required(),
      password:Joi.string().regex(/^[a-zA-Z0-9]{3,30}$/).required(),
      confirm_password:Joi.string().equal(Joi.ref('password')).messages({'any.only': 'password does not match' }).required(),
      firstname: Joi.string().required(),
      lastname: Joi.string(),
      email: Joi.string().email({minDomainSegments: 2}).required(),
      phonenumber: Joi.string().min(6).regex(/^([+])?(\d+)$/).required(),
      dob: Joi.date().max('01-01-2003').iso().messages({'date.format': `Date format is YYYY-MM-DD`,'date.max':`Age must be 18+`}).required(),
      sex: Joi.string().valid('male', 'female','transger', 'others')

    });

    // Validate req.body against the defined schema
    const validation = schema.validate(req.body);
    const { value, error } = validation;

    if (error) {
      const message = error.details.map(x => x.message);

      res.status(400).json({
        status: "error",
        message: "Invalid request data",
        data: message
      });
    } else {
      res.json({
        status: "success",
        message: "Registration successful",
        data: value
      });
    }
  } catch (error) {
    res.json({status:"failed",message:error.message})
  }
});

Joi.object() instantiate a Joi schema object to work with. The schema require Joi.object() to process validation and other Joi features.
I will be explaining the schema constraints and if I have explained some constraints already I won't need to repeat e.g if I have explained .string() I won't be repeating it again in order to keep the article short and simple.

The constraints for username include:

  • .string() a string.

NOTE: "s" is in lowercase, not uppercase i.e if you use .String() joi will throw this error Joi.String is not a function

  • min(6) - at least 6 characters long
  • .max(30) - not more than 30 characters
  • .alphanum() - contain alphanumeric characters e.g (olufemi78)
  • .uppercase() - This means when the user input username, joi should convert to uppercase (interesting yeah? πŸ˜‰)
  • required() - This means username is required, if user does not pass it, joi will throw error "\"username\" is required"

The constraints for password include:

  • .regex('^[a-zA-Z0-9]{3,30}$') - This means, it must satisfy the custom regex pattern.

Here's the explanation of the regex pattern

^ : Asserts the start of a string

    [a-zA-Z0-9]{3,30} : Matches any character from a-z or 0-9 but the length range must between 3 to 30

    $ : End

You can use regex101 to play with regex. The right pane explains it token by token.

The constraints for confirm_password include:

  • .equal(Joi.ref('password')) - This means it reference password and it must be equal to password.
  • messages({ 'any.only': 'password does not match' }) - If password and confirm_password does not match, joi will throw this custom error password does not match . any.only is error type so whenever the error type is any.only I prefer to send custom message and not the typical joi error message. Just make's the error message more descriptive to the user.

The constraints for firstname and lastname:

  • username name is required while lastname is not required

The constraints for email include:

  • .email({minDomainSegments: 2}) - It must be a valid email string and must have two domain parts e.g. user.com . One fascinating thing about email validation with joi is that you can decide the top-level domains (TLDs) you want. e.g; you want only .com and .net .email({ minDomainSegments: 2, tlds: { allow: ['com', 'net'] } }) This will only allow .com and .net

The constraints for phonenumber include:

  • It must be a string with digits in the format based on the regex pattern e.g +248888888888

The constraints for dob include:

  • Joi.date().max('01-01-2003').iso() - it must be a valid date in ISO 8601 format and it cannot be after Jan 1, 2003. This is super useful if you want to ensure a certain age range can not register on your platform e.g a betting website allow only 18+.
  • messages({'date.format': Date format is YYYY-MM-DD
  • 'date.max':Age must be 18+})- This means if user enter invalid date format and joi throws error, rather sending back the not too fancy joi error message, send a custom message. Custom messages breakdown:
  • 'date.format': means the error type i.e if joi is throwing error type that is 'date.format', then send this custom message Date format is YYYY-MM-DD to the user. if error type is 'date.max': then send this custom message Age must be 18+ . If there's no custom message for this particular error type (date.max), joi will throw this '"dob" must be less than or equal to "2003-01-01T00:00:00.000Z"'. To aid user experience, I decided to make it easy by sending "Age must be 18+" rather than joi error message. You can literally send any message you pleased. Since it's a custom message, you're fully in control of the message. That's really cool πŸ‘Œ

The constraints for sex include:

  • .valid('male', 'female','transger') - This means only 4 options are accepted i.e male, female, transgender and others. If user enter any other option aside from the 4, joi will throw this error "\"sex\" must be one of [male, female, transger, others]". This come in handy for analytics. You can easily know the genders that register on your platform. const validation = schema.validate(req.body);

This line of code takes in the data from the body request and validates it against the schema already defined.

const { value, error } = validation;
        if (error) {
          const message = error.details.map(x => x.message);

          res.status(422).json({
            status: "error",
            message: "Invalid request data",
            data: message
          });

What I did here is to destructure the joi response object after validation and use map() to pick out just the error message and not the whole error object.

Everything is set now. Lets enjoy the thrills and superpower of joi πŸ”₯πŸ”₯πŸ”₯

EXTRAS:
So far we've seen string, number, email etc validation. What about arrays, Objects, and array of objects? Yeah, I got you.

Array

meta:Joi.array()

The above validates an array.

Object

meta:Joi.object()

The above validates an Object.

Joi.alternatives()

Joi.alternatives() can be used to pass many alternatives like array, object, string etc. For example, in a scenario where you're either expecting an array or object, especially when you depend on an external API and you can't be so sure if the response would be an array or an object.

meta:Joi.alternatives().try(Joi.object(), Joi.array())

Finally, you can go step further to validate the items in the array or object.

Joi.array().items(Joi.string())
Joi.array().items(Joi.object())

Joi is super cool and I would recommend it for server-side validation.

Check Joi docs here

See Project here

Do you like this article? Hit me up on twitter or linkedin

24