24
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
- 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 referencepassword
and it must be equal to password. -
messages({ 'any.only': 'password does not match' })
- Ifpassword
andconfirm_password
does not match, joi will throw this custom errorpassword does not match
.any.only
is error type so whenever the error type isany.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 whilelastname
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 messageDate format is YYYY-MM-DD
to the user. if error type is 'date.max':
then send this custom messageAge 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.emale
,female
,transgender
andothers
. 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.
meta:Joi.array()
The above validates an array.
meta:Joi.object()
The above validates an Object.
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
24