Hapi.js Validation with Joi + failAction
question.
Situation
We want to build a "traditional" server-side-only rendered application using Hapi.
I'm trying to understand how to avoid returning a "raw" 400
error to the client when Joi
validation fails:
We want to intercept this "email not allowed to be empty" validation error and display it in the html template back to the client,
instead of simply returning the 400
error.
@AdriVanHoudt advised that we should:
"Look at failAction under "
So we added failAction: 'log'
to the /register
route handler:
{
method: '*',
path: '/register',
config: {
validate: {
payload : register_fields,
failAction: 'log'
}
},
handler: register_handler
}
See code in: server.js
the register_handler
is:
function register_handler(request, reply, source, error) {
console.log(request.payload);
console.log(' - - - - - - - - - - - - - - - - - - - - -');
console.log(source)
console.log(' - - - - - - - - - - - - - - - - - - - - -');
console.log(error)
return reply('wele!');
}
I am expecting to see an error in the terminal/console
but when I try to console.log
the handler
:
- - - - - - - - - - - - - - - - - - - - -
undefined
- - - - - - - - - - - - - - - - - - - - -
undefined
I asked the question on GitHub: but have not yet got an answer with a good example. Full code if you have time to help:
Hapi.js Validation with Joi + failAction
question.
Situation
We want to build a "traditional" server-side-only rendered application using Hapi.
I'm trying to understand how to avoid returning a "raw" 400
error to the client when Joi
validation fails:
We want to intercept this "email not allowed to be empty" validation error and display it in the html template back to the client,
instead of simply returning the 400
error.
@AdriVanHoudt advised that we should:
"Look at failAction under http://hapijs./api#route-options "
So we added failAction: 'log'
to the /register
route handler:
{
method: '*',
path: '/register',
config: {
validate: {
payload : register_fields,
failAction: 'log'
}
},
handler: register_handler
}
See code in: server.js
the register_handler
is:
function register_handler(request, reply, source, error) {
console.log(request.payload);
console.log(' - - - - - - - - - - - - - - - - - - - - -');
console.log(source)
console.log(' - - - - - - - - - - - - - - - - - - - - -');
console.log(error)
return reply('wele!');
}
I am expecting to see an error in the terminal/console
but when I try to console.log
the handler
:
- - - - - - - - - - - - - - - - - - - - -
undefined
- - - - - - - - - - - - - - - - - - - - -
undefined
I asked the question on GitHub: https://github./hapijs/joi/issues/725 but have not yet got an answer with a good example. Full code if you have time to help: https://github./nelsonic/hapi-validation-question
Share Improve this question asked Oct 1, 2015 at 22:18 nelsonicnelsonic 33.2k21 gold badges95 silver badges124 bronze badges2 Answers
Reset to default 11There are Two Simple Solutions:
1. Use server.ext('onPreResponse' ...
As noted by @Clarkie a generic way of catching all errors in your Hapi App is to use 'onPreResponse'
.
We wrote a Hapi Plugin that does exactly that: https://www.npmjs./package/hapi-error
As usual it has:
And lets you define your own custom error pages in 3 easy steps.
1. Install the plugin from npm:
npm install hapi-error --save
2. Include the plugin in your Hapi project
Include the plugin when you register
your server:
See: /example/server_example.js for simple example
3. Ensure that you have a View called error_template
Note:
hapi-error
plugin expects you are usingVision
(the standard view rendering library for Hapi apps) which allows you to use Handlebars, Jade, React, etc. for your templates.
Your error_template.html
(or error_template.ext
error_template.jsx
) should make use of the 3 variables it will be passed:
errorTitle
- the error tile generated by HapistatusCode
- *HTTP statusCode sent to the client e.g:404
(not found)errorMessage
- the human-friendly error message
for an example see:
/example/error_template.html
That's it!
2. Use failAction
We added failAction
which re-uses the register_handler
so that the registration-form.html
is shown with any input validation error message (until it is submitted with valid data)
{
method: '*',
path: '/register',
config: {
validate: {
payload : register_fields,
failAction: register_handler // register_handler is dual-purpose (see below!)
}
},
handler: register_handler
}
the register_handler
is:
function register_handler(request, reply, source, error) {
// show the registration form until its submitted correctly
if(!request.payload || request.payload && error) {
var errors, values; // return empty if not set.
if(error && error.data) { // means the handler is dual-purpose
errors = extract_validation_error(error); // the error field + message
values = return_form_input_values(error); // avoid wiping form data
}
return reply.view('registration-form', {
title : 'Please Register ' + request.server.version,
error : errors, // error object used in html template
values : values // (escaped) values displayed in form inputs
}).code(error ? 400 : 200); // HTTP status code depending on error
}
else { // once successful, show wele message!
return reply.view('wele-message', {
name : validator.escape(request.payload.name),
email : validator.escape(request.payload.email)
})
}
}
See: server.js:57 for plete file.
Where extract_validation_error(error)
and return_form_input_values(error)
are helper functions defined within server.js
(but would be split out into re-useable view helpers) which keep our handler function lean.
When we submit the form without any of the required fields we see:
We also use https://github./chriso/validator.js to mitigate Cross Site Scripting vulnerability:
And display a wele message on successful registration:
Conclusion
We feel that re-using the handler function as the failAction
keeps the code related to this route/action in a single place
whereas server.ext('onPreResponse' ...
(while appropriate on initial inspection) will introduce "hooks"
which can be a source of confusion (once an app has many such hooks...)
#YMMV
Let us know what you think!
You should look at implementing an error handler in the onPreResponse
extension point.
The response contained in request.response may be modified (but not assigned a new value). To return a different response type (for example, replace an error with an HTML response), return a new response via reply(response). Note that any errors generated after reply(response) is called will not be passed back to the
onPreResponse
extension method to prevent an infinite loop.
A simple example:
server.ext('onPreResponse', function (request, reply) {
if (request.response.statusCode === 400 ){
return reply('summat else');
}
return reply.continue();
});