Package Exports
- hapi-error
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (hapi-error) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
hapi-error
Intercept errors in your Hapi web app/api and send a useful message to the client.
Why?
By default, Hapi
does not give people friendly error messages.
This plugin lets your app display consistent, friendly & useful
error messages in your Hapi apps.
What?
Under the hood, Hapi uses
Boom
to handle errors. These errors are returned as JSON
. e.g:
If a URL/Endpoint does not exist a 404
error is returned:
When a person/client attempts to access a "restricted" endpoint without
the proper authentication/authorisation a 401
error is shown:
And if an unknown error occurs on the server, a 500
error is thrown:
The hapi-error
plugin re-purposes the Boom
errors (both the standard Hapi errors and your custom ones) and instead display human-friendly error page:
Note: super basic error page example is just what we came up with in a few minutes, you have full control over what your error page looks like, so use your imagination!
Note: if the client expects a JSON response simply define that in the
headers.accept
and it will still receive the JSON erro messages.
How?
Error handling in 3 easy steps:
1. Install the plugin from npm:
npm install hapi-error --save
2. Include the plugin in your Hapi project
Includ the plugin when you register
your server:
var Hapi = require('hapi');
var Path = require('path');
var Boom = require('boom');
var server = new Hapi.Server();
server.connection({ port: process.env.PORT || 8000 });
server.route([
{
method: 'GET',
path: '/',
config: {
handler: function (request, reply) {
reply('hello world');
}
}
},
{
method: 'GET',
path: '/error',
config: {
handler: function (request, reply) {
reply(new Error('500'));
}
}
}
]);
// this is where we include the hapi-error plugin:
server.register([require('hapi-error'), require('vision')], function (err) {
if (err) {
throw err;
}
server.views({
engines: {
html: require('handlebars') // or Jade or React etc.
},
path: Path.resolve(__dirname, './../lib')
});
server.start(function (err) {
if (err) {
throw err;
}
console.log('Visit:', server.info.uri);
});
});
module.exports = 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!
Want more...? 😉
Custom Error Messages using Hoek.assert
Hoek
(a utility library extensively used internally by Hapi) has an assert
method which allows
you to "throw" errors one line in your code.
Consider the following Hapi route handler code that is fetching data from a generic Database:
function handler (request, reply) {
db.get('yourkey', function (err, data) {
if (err) {
return reply('error_template', { msg: 'A database error occurred'});
} else {
return reply('amazing_app_view', {data: data});
}
});
}
This can be re-written (simplified) using Hoek.assert
var Hoek = require('hoek'); // require Hoek somewhere in your code
function handler (request, reply) {
db.get('yourkey', function (err, data) { // much simpler, right?
Hoek.assert(!err, 'A database error occurred');
return reply('amazing_app_view', {data: data});
}); // this has *exactly* the same effect in much less code.
}
Explanation:
Hoek.assert(!err, 'A database error occurred');
Hoek asserts that there is no error. Which means that if
there is an error, it will be "thrown" with the message you define in the second argument.
Output:
Redirecting to another endpoint
Sometimes you don't want to show an error page; instead you want to re-direct to another page. For example, when your route/page requires the person to be authenticated, but they have not supplied a valid session/token to view the route/page.
In this situation the default Hapi behaviour is to return a 401
(unauthorized) error,
however this is not very useful to the person using your application.
Redirecting to a specific url is easy with hapi-error
:
const redirectConfig = {
"401": { // if the statusCode is 401 redirect to /login page/endpoint
"redirect": "/login"
}
}
server.register([{
register: require('hapi-error'),
options: redirectConfig // pass in your redirect configuration in options
},
require('vision')], function (err) {
// etc.
});
This will redirect
the client/browser to the /login
endpoint
and will append a query parameter with the url the person was trying to visit.
e.g: GET /admin --> 401 unauthorized --> redirect to /login?redirect=/admin
Redirect Example: /example/redirect_server_example.js
Are Query Parmeters Preserved?
Yes! e.g: if the original url is /admin?sort=desc
the redirect url will be: /login?redirect=/admin?sort=desc
Such that after the person has logged in they will be re-directed
back to to /admin?sort=desc
as desired.
And it's valid to have multiple question marks in the URL see: http://stackoverflow.com/questions/2924160/is-it-valid-to-have-more-than-one-question-mark-in-a-url so the query is preserved and can be used to send the person to the exact url they requested after they have successfully logged in.
---
Under the Hood / Implementation Detail:
When there is an error in the request/response cycle,
the Hapi request
Object has useful error object we can use.
Try logging the request.response
in one of your Hapi route handlers:
console.log(request.response);
A typical Boom
error has the format:
{ [Error: 500]
isBoom: true,
isServer: true,
data: null,
output:
{ statusCode: 500,
payload:
{ statusCode: 500,
error: 'Internal Server Error',
message: 'An internal server error occurred' },
headers: {} },
reformat: [Function] }
The way to intercept this error is with a plugin that gets invoked before the response is returned to the client.
A simple Error Handler Plugin example:
/**
* register defines our error_handler plugin
*/
exports.register = function error_handler (server, options, next) {
// onPreResponse intercepts all errors
server.ext('onPreResponse', function (request, reply) {
var req = request.response;
// console.log(request.response);
if (req.isBoom) { // reply with a slightly more user-friendly error message
return reply('Sorry, something went wrong, please retrace your steps.')
.code(req.output.payload.statusCode);
}
reply.continue();
});
next(); // continue with other plugins
};
exports.register.attributes = {
pkg: require('../package.json')
};
This is the basic setup for you can customise in your Hapi app.
However if you want a "turnkey"
plugin you can use in your project with user-friendly HTML error pages
(when the client requests HTML
) and App/API-friendly JSON error responses
(when the client asks for JSON
) then see the code in /lib/index.js
and usage instructions above!
Background Reading & Research
- Writing useful / friendly error messages: https://medium.com/@thomasfuchs/how-to-write-an-error-message-883718173322