Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to avoid Hapi.js sending 400 error when Validating Request with Joi

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:

register-iphone4s-sim

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.com/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('welcome!');
}

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.com/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.com/nelsonic/hapi-validation-question

like image 342
nelsonic Avatar asked Oct 01 '15 22:10

nelsonic


People also ask

Which is better express validator or Joi?

Express-validator So by definition, we can say that: Joi can be used for creating schemas (just like we use mongoose for creating NoSQL schemas) and you can use it with plain Javascript objects. It's like a plug n play library and is easy to use. On the other hand, express-validator uses validator.

What is Joi Hapi JS?

Joi is an object schema description language and validator for JavaScript objects. Joi allows you to create blueprints or schemas for JavaScript objects to ensure validation of key information.

Is Joi deprecated?

@hapi/joi is deprecated #3430 This isn't exactly a bug, but joi is moving out of the @hapi org. Probably a good idea of docusaurus v2 moves to the current version, too.


2 Answers

There 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.com/package/hapi-error

As usual it has:

Build Status codecov.io Code Climate Dependency Status devDependency Status HitCount

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 using Vision (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 Hapi
  • statusCode - *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!

hapi-error-screens

2. Use failAction

Build Status codecov.io Code Climate HitCount

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 welcome message!
    return reply.view('welcome-message', {
      name   : validator.escape(request.payload.name),
      email  : validator.escape(request.payload.email)
    })
  }
}

See: server.js:57 for complete 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:

register-1of4

register-3of4

We also use https://github.com/chriso/validator.js to mitigate Cross Site Scripting vulnerability:

register-hack-1of2

And display a welcome message on successful registration: reg-success-1of2

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! Join the chat at https://gitter.im/dwyl/chat

like image 191
nelsonic Avatar answered Oct 29 '22 03:10

nelsonic


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();
});
like image 40
Clarkie Avatar answered Oct 29 '22 03:10

Clarkie