Validation

It’s often desirable to validate data passed to the route, especially so whilst building an API. Hapi makes this a breeze by using the Joi object schema validation module, also developed by Spumko.

First, let’s use npm to install the Joi module and save it to our project’s dependencies.



npm WARN package.json MyApp@0.0.1 No repository field.

npm WARN package.json MyApp@0.0.1 No README data

npm http GET

npm http 304

npm http GET

npm http 304

joi@4.6.0 node_modules/joi

└── hoek@2.2.0 bash-3.2$ npm install joi@4.x --savenpm WARN package.json MyApp@0.0.1 No repository field.npm WARN package.json MyApp@0.0.1 No README datanpm http GET https://registry.npmjs.org/joi npm http 304 https://registry.npmjs.org/joi npm http GET https://registry.npmjs.org/hoek npm http 304 https://registry.npmjs.org/hoek joi@4.6.0 node_modules/joi└── hoek@2.2.0

To begin using Joi validation, we need to require the module. Add the following line after we require Hapi.

var Joi = require("joi");

We use the config key in the route object to express validation rules. The config key is an object that allows us to split the route information from its implementation, and allows the definition of validation rules, cache settings and more.

Let’s define our route configuration.

var helloConfig = {

handler: function(request, reply) {

var names = request.params.name.split("/");

reply({

first: names[0],

last: names[1],

mood: request.query.mood

});

},

validate: {

params: {

name: Joi.string().min(8).max(100)

},

query: {

mood: Joi.string().valid(["neutral","happy","sad"]).default("neutral")

}

}

};

The object now contains the handler — which can either be a part of the config object or the route object — and Joi schemas.

The validate key can be used to define schemas matching against:

query — query string components

payload — the request body

params — parameterized path segments

In this case, our configuration says the following:

The name segment of the path must be at least eight characters long , and at maximum, 100 characters long .

segment of the path must be , and . The mood query string component is only valid if it’s value is either “neutral”, “happy” or “sad”. If the component is omitted from the request, Joi will set it to “neutral”, freeing us from doing this in our handler.

Our modified route is as follows:

server.route({

path: "/hello/{name*2}",

method: "GET",

config: helloConfig

});

Restart the Hapi server and make a request to http://localhost:8080/hello/J/Doe and we will be presented with the following object, containing a human-readable explanation as to why the request is not valid.

{"statusCode":400,"error":"Bad Request","message":"the length of name must be at least 8 characters long","validation":{"source":"path","keys":["name"]}}

If we request http://localhost:8080/hello/John/Doe we will get the same JSON object as we got before.

{"first":"John","last":"Doe":"mood":"neutral"}

Let’s set the mood to “excited”, and see what response we get. Make a request to http://localhost:8080/hello/John/Doe?mood=excited.

{"statusCode":400,"error":"Bad Request","message":"the value of mood must be one of neutral, happy, sad","validation":{"source":"query","keys":["mood"]}}

Again, Hapi sends us a concise error message specifying both a human-readable error message and the specific area that Joi reported wasn’t valid.

Let’s go one step further and add a query parameter for the person’s age. Modify the route’s configuration to the following (we’re adding the age key to the response, and a validation rule for Joi).

var helloConfig = {

handler: function(request, reply) {

var names = request.params.name.split("/");

reply({

first: names[0],

last: names[1],

mood: request.query.mood,

age: request.query.age

});

},

validate: {

params: {

name: Joi.string().min(8).max(20)

},

query: {

mood: Joi.string().valid(["neutral","happy","sad"]).default("neutral"),

age: Joi.number().integer().min(13).max(100)

}

}

};

When we request http://localhost:8080/hello/John/Doe?age=19 we’ll be sent the following JSON object:

{"first":"John","last":"Doe","mood":"neutral","age":19}

Notice that “age” gets converted to a Number automatically by Joi before being assigned to request.query. If we attempt to specify age as something other than an integer, such as a string or floating point number, the validation won’t pass.

For example, if we request http://localhost:8080/hello/John/Doe?age=no we’ll get a Joi error.

{"statusCode":400,"error":"Bad Request","message":"the value of age must be a number","validation":{"source":"query","keys":["age"]}}

Joi provides a lot of neat validation functions, which you can find out about on its GitHub repository.