Validation; joi brings you "Joy" 😁
Tags: node, api, javascript, express
Published: 2021-07-23 14:36:54

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 💻💻💻


```javascript

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**

   ```javascript
 ^ : 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](http://regex101.com/#pcre)  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.


```javascript
        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 🔥🔥🔥

![](https://paper-attachments.dropbox.com/s_EB38424D386749E635CCDF8757FD937DB6738220A46C64FBDC64FF4DB41D7B53_1610300851439_Screenshot+2021-01-10+at+18.44.18.png)



![](https://paper-attachments.dropbox.com/s_EB38424D386749E635CCDF8757FD937DB6738220A46C64FBDC64FF4DB41D7B53_1610287972263_Screenshot+2021-01-10+at+14.49.22.png)



![](https://paper-attachments.dropbox.com/s_EB38424D386749E635CCDF8757FD937DB6738220A46C64FBDC64FF4DB41D7B53_1610287972278_Screenshot+2021-01-10+at+14.49.41.png)

![](https://paper-attachments.dropbox.com/s_EB38424D386749E635CCDF8757FD937DB6738220A46C64FBDC64FF4DB41D7B53_1610287972286_Screenshot+2021-01-10+at+14.59.52.png)



![](https://paper-attachments.dropbox.com/s_EB38424D386749E635CCDF8757FD937DB6738220A46C64FBDC64FF4DB41D7B53_1610287972296_Screenshot+2021-01-10+at+15.01.35.png)



![](https://paper-attachments.dropbox.com/s_EB38424D386749E635CCDF8757FD937DB6738220A46C64FBDC64FF4DB41D7B53_1610287972308_Screenshot+2021-01-10+at+15.02.24.png)


**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.

![](https://paper-attachments.dropbox.com/s_EB38424D386749E635CCDF8757FD937DB6738220A46C64FBDC64FF4DB41D7B53_1626456094873_Screenshot+2021-07-16+at+17.54.16.png)

![](https://paper-attachments.dropbox.com/s_EB38424D386749E635CCDF8757FD937DB6738220A46C64FBDC64FF4DB41D7B53_1626456809071_Screenshot+2021-07-16+at+17.52.54.png)


### Object

    meta:Joi.object()

The above validates an Object.

![](https://paper-attachments.dropbox.com/s_EB38424D386749E635CCDF8757FD937DB6738220A46C64FBDC64FF4DB41D7B53_1626456162558_Screenshot+2021-07-16+at+17.55.15.png)

![](https://paper-attachments.dropbox.com/s_EB38424D386749E635CCDF8757FD937DB6738220A46C64FBDC64FF4DB41D7B53_1626456168049_Screenshot+2021-07-16+at+17.56.05.png)


### 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())


![](https://paper-attachments.dropbox.com/s_EB38424D386749E635CCDF8757FD937DB6738220A46C64FBDC64FF4DB41D7B53_1626456325713_Screenshot+2021-07-16+at+17.52.54.png)

![](https://paper-attachments.dropbox.com/s_EB38424D386749E635CCDF8757FD937DB6738220A46C64FBDC64FF4DB41D7B53_1626456417885_Screenshot+2021-07-16+at+17.56.05.png)



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

    Joi.array().items(Joi.string())
![](https://paper-attachments.dropbox.com/s_EB38424D386749E635CCDF8757FD937DB6738220A46C64FBDC64FF4DB41D7B53_1626456457657_Screenshot+2021-07-16+at+18.05.24.png)

![](https://paper-attachments.dropbox.com/s_EB38424D386749E635CCDF8757FD937DB6738220A46C64FBDC64FF4DB41D7B53_1626456482610_Screenshot+2021-07-16+at+18.05.39.png)

    Joi.array().items(Joi.object())
![](https://paper-attachments.dropbox.com/s_EB38424D386749E635CCDF8757FD937DB6738220A46C64FBDC64FF4DB41D7B53_1626456508612_Screenshot+2021-07-16+at+18.09.16.png)



![](https://paper-attachments.dropbox.com/s_EB38424D386749E635CCDF8757FD937DB6738220A46C64FBDC64FF4DB41D7B53_1626456757429_Screenshot+2021-07-16+at+18.32.12.png)


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

Check Joi docs  [here](https://joi.dev/) 

See Project  [here](https://glitch.com/edit/#!/joi-validation) 

Do you like this article? Hit me up on  [twitter](https://twitter.com/femioba_)  or  [linkedin](https://www.linkedin.com/in/olufemi-obafunmiso/)