Validation is like flossing: you know you should do it, but it’s easy to skip. And just like flossing, if you neglecting your validation, you’re asking for trouble.
The Joi module is Hapi’s answer to validation, but you don’t need to be using Hapi in order to use Joi. In this tutorial, we’ll show how we can use Joi to validate any object, and then we’ll take a look at how easily we can use Joi to validate requests made to Hapi applications.
Joi! Joi! Joi!
Let’s play. Save the following into
1 2 3 4 5 6 7 8 9 10 11 12
Check out line 10, where we call
Joi.validate(data, schema, config). It matches the data against the
schema and returns an error if anything’s wrong. If everything in the
data object is valid according to the schema, the call returns
HC SVNT DRACONES: Returning null on success really is the best API, but it can lead to some counter-intuitive if statements. Be sure to test your code.
If you don’t already have a
package.json file, create one with
npm init, and
then install the Joi module with
npm install joi --save.
node and follow along with the commands shown after the
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
Because we used
.required() we have to specify the username key, and
because we used
.with('birthyear') we have also to include the birthyear key
whenever we have a username.
.with constraint is more typically
used with non-required values. In this example, it probably makes more sense
.required() on both username and birthyear, but we’re using a simplified
version of the example in the Joi README.
Now let’s give Joi some seriously bad data:
1 2 3 4 5 6 7 8
Even though the birthyear violates the
.min(1900) constraint, we only
got back an error about the username. This is because the
option defaults to true.
To make Joi send back all the errors, we use a config object
abortEarly set to true.
This gets passed as the third parameter to
Joi.validate(data, schema, config).
1 2 3 4 5 6 7 8 9 10 11
Check out the Joi docs to see the seven other options you can give validate.
The object returned by
It has a
message key, and
my_joi_error instanceof Error
will return true.
If you want to have your program blow up on an Joi error, you can throw that object directly:
If you’re using Joi in node, you’d probably use something more like this:
1 2 3 4 5
Schemas Big and Small
The schema definition can be arbitrarily deep, but in the end it must terminate
in a Joi schema object. Above, we saw schema objects created by
Schema objects also have a
.validate() method, and like
returns either an Error object or
null. But instead of taking
a schema and a config, it only takes a single value.
When you use
Joi.validate(), Joi effectively iterates over all the keys in your
object, matching each to a corresponding schema object in your schema, and
If there are missing keys, that’s fine, unless you said they’re
1 2 3
Extra keys will return an error, unless you set the
If you’re validating an object that has keys which refer to simple data as well as keys that refer to functions, you have two choices.
First, you can explicitly set up rules that those keys should be functions:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
Second, you could set either the
allowUnknown or the
option to true. Using
skipFunctions will allow unknown keys only if they’re
1 2 3 4 5 6 7 8 9 10 11 12 13
Joi uses a fluent interface to allow you to specify exactly what values pass the test. If you’ve used jQuery, you’ve already used a fluent interface. It lets you chain together method calls and keep getting back the same general type of object.
To start, you’ll need to call one of these factory functions:
Joi.string(). They do what they say on the
Joi.any() accepting absolutely thing. It seems useless, but it’s
worth checking out the methods defined on Joi.any()
because they’re inherited by all the other schema types we’ll see later.
You can call the
.allow() method to whitelist a value or array of values, regardless
of the other restrictions on the schema.
.valid() method is similar, but it makes the resulting schema match only the
values passed into a call to
1 2 3 4 5 6
That example is a bit weird, since we completely blow away the usefulness
Joi.number(), but it illustrates the difference between the two methods.
We can also use the
.invalid() method to blacklist a value or an array of values.
If you have multiple calls to valid and invalid, they combine and the later calls
1 2 3 4 5 6 7 8 9 10
We’ve already seen the
.required() method which means a key can’t be undefined.
We can establish relationships between keys by using the
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
The other types are fairly self explanatory
Joi.string(). They’ve all got their options, so be sure to
check out the documentation.
Joi.object() is a bit different from the others. It’s the only
schema factory that can take a parameter. You can pass in a schema
just like the second parameter to
Joi.validate(data, schema, config).
If you just call
Joi.object() it will match anything where
is ‘object’. If you give it a schema, it will only match objects that
exactly match that schema, and all the normal rules about missing keys apply.
Joi.object() factory is a covenient way to say “I don’t want to specify my
schema any deeper.” But remember it only matches objects:
1 2 3 4 5 6
Joi.object() is also very useful when you need reuse a matcher on different
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
Hapi Hapi Joi Joi
Since Joi was created as part of the Hapi project, it’s no suprise that Joi schemas plug easily into Hapi apps. You can add a Joi schema object to a route’s config to validate the query, the payload or the path.
Here we validate the query params to make sure they’re only digits and we never include one param without the other.
We don’t even need to add the Joi module to our
package.json if we’re using
Hapi becuase Joi is already available as
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
Fire it up, and these requests will succeed:
But these will fail with a 400 Bad Request:
1 2 3
Easy beansy. Once again, Hapi prefers a config-oriented approach. This is a boon for testing, since you could test your entire app’s functionality with shot or PhantomJS, but you could also snag that Joi object and test it without ever having to start a server!
You get a lot out of Joi. Solid validation with a convenient interface. It makes it a breeze to lock down your APIs so that you only get the input you expect.