Joi is a powerful JavaScript validation library. You can use it to validate the input you receive to your API, among other uses.
While it comes pretty good error messages out of the box, let’s see some ways to customize them.
You can test any of the examples below using Joi’s playground.
Adding custom labels
Let’s define a schema that accepts a matrix
2D array field.
Joi.object({ matrix: Joi.array().items(Joi.array().items(Joi.number())) })
The following object wouldn’t pass validation because it has a string a
among its elements.
{ matrix: [[0, 1, 2], ['a', 1, 2]] }
By default, Joi returns the following error message:
Validation Error: "matrix[1][0]" must be a number
The error message includes the full path of the invalid value. The top-most key is matrix
. Then, each array element is identified by its array index, starting from 0
.
We can override the default key with label
:
Joi.object({ matrix: Joi.array().items( Joi.array().items(Joi.number().label("matrix value")) ), });
Validation Error: "matrix value" must be a number
We can do the same for any key. Or even specify labels for each array element.
For example, let’s add a label for the elements passed to the matrix
array.
Joi.object({ matrix: Joi.array().items( Joi.array().items(Joi.number()).label("matrix row") ), });
Now, the error message will be::
{ matrix: [[0, 1, 2], 'a'], }
Validation Error: "matrix row" must be an array
Return the key name instead of full path in error messages
Let’s define the following deeply nested schema:
const schema = Joi.object({ a: { b: { c: { d: { e: Joi.number(), }, }, }, }, });
Validating the following object, yields the error message:
const obj = { a: { b: { c: { d: { e: "a", }, }, }, }, };
Validation Error: "a.b.c.d.e" must be a number
See the faulty field is identified with its full path in the object.
We can tweak this behavior by setting the errors.label
option to key
. The default value is path
.
schema.validate(obj, {errors: {label: 'key'}})
Validation error: "e" must be a number
Additionally, we can delete the quotes that wrap the label with the errors.wrap
option:
schema.validate(obj, {errors: {label: 'key', wrap: {label: false}}})
Validation error: e must be a number
See more information about the wrap
and label
options in the any.validate
documentation
Add custom error messages to Joi validation
To pass custom error messages, we first need to identify the associated error type. For example, a numerical field being less than the specified minimum value is associated to a number.min
error type.
Once we have identified the error type, we can ask Joi to override the predefined error message with the messages
method.
Joi.object({ age: Joi.number() .integer() .min(18) .messages({ "number.min": "You must be at least 18 years old" }), });
See the messages
method accepts an object where each key corresponds to the error type and the value is the desired error message.
With this configuration, the following object will return the error message:
{ age: 1, }
Validation Error: You must be at least 18 years old
You can find the full list of error types in Joi’s documentation.
Referencing the values of other fields in error messages
Let’s include the actual field values in the error message. We can do so with Joi’s templating syntax.
Let’s create a schema that requires both a name
and age
fields.
Joi.object({ name: Joi.string().required(), age: Joi.number() .required() .messages({ "any.required": "You must tells us your age, {name}" }), });
If we don’t set the age
, Joi will return an error message with the value passed to the name
field. See that the field names was wrapped inside {}
.
{ name: "John", }
Validation Error: You must tells us your age, John
You can find more about template syntax in Joi’s documentation.
Referencing fields in deeply nested objects
Let’s modify the schema above by nesting age
inside a data
field.
Joi.object({ name: Joi.string().required(), data: { age: Joi.number() .required() .messages({ "any.required": "You must tells us your age, {name}" }), }, });
Now, let’s pass the following object to validate:
{ name: "John", data: {}, }
We’ll receive a custom error message, but Joi didn’t catch the name
this time.
Validation Error: You must tells us your age,
By default, the key will recognize only siblings of the validated field. In our case, those are all the properties of the data
field. To be more specific we have to use Joi references.
A simple solution in our case is to reference the parent of name
with relative references:
Joi.object({ name: Joi.string().required(), data: { age: Joi.number() .required() .messages({ "any.required": "You must tells us your age, {...name}" }), }, });
See that we’re prepending the field name with three dots: ...name
.
This is s similar to the rules of path resolution in the command line. ..
references the parent field (default behavior), and .
references the current field. We can go higher up the hierarchy by prepending additional dots.
Googling and googling and I ended up here cuz there are no good docs even in joi site, this post crearly stated the things that I needed to know
Thank you very much!
I’m glad this was helpful. You are welcome!