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 error 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 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 Riot or React etc.
},
path: Path.resolve(__dirname, '/your/view/directory')
});
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, Riot, React, etc. for your templates.
Your error_template.html
(or error_template.ext
error_template.tag
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...? ask!
Custom Error Messages using request.handleError
When you register
the hapi-error
plugin a useful handleError
method
becomes available in every request handler which allows you to (safely)
"handle" any "thrown" errors using just one line of 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?
request.handleError(!err, 'A database error occurred');
return reply('amazing_app_view', {data: data});
}); // this has *exactly* the same effect in much less code.
}
Explanation:
Under the hood, request.handleError
is using Hoek.assert
which
will assert
that there is no error e.g:
Hoek.assert(!err, 'A database error occurred');
Which means that if there is an error, it will be "thrown" with the message you define in the second argument.
Output:
Need to call handleError
outside of the context of the request
?
Sometimes we create handlers that perform a task outside of the context of
a route/handler (e.g accessing a database or API) in this context
we still want to use handleError
to simplify error handling.
This is easy with hapi-error
, here's an example:
var handleError = require('hapi-error').handleError;
db.get(key, function (error, result) {
handleError(error, 'Error retrieving ' + key + ' from DB :-( ');
callback(err, result);
});
Want to pass some more/custom data your error_template.html?
All you have to do is pass an object to request.handleError
with an
errorMessage property and any other template properties you want!
For example,
request.handleError(!error, {errorMessage: 'Oops - there has been an error',
email: 'example@example.example', colour:'blue'});
You will then be able to use {{email}} and {{colour}} in your error_template.html
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: /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.
See: lib/index.js for details on how the plugin is implemented.
If you have any questions, just ask!
Background Reading & Research
- Writing useful / friendly error messages: https://medium.com/@thomasfuchs/how-to-write-an-error-message-883718173322