catch errors in your hapi application and display the appropriate error message/page
npm install hapi-errorhapi-errorIntercept errors in your Hapi web app/api
and send a useful message to the client.










> #### Seeing an (_unhelpful/unfriendly_) error message is _by far_ the _most frustrating_ part of the "User _Experience_" (UX) of your web app/site.
Most _non-technical_ people (_"average" web users_) have _no clue_
what a 401 error is. And if you/we the developer(s) do not _communicate_ with them, it can quickly lead to confusion and
_abandonment_!
If instead of simply displaying 401 we _inform_ people:"Please login to see that page." we _instantly improve_
the UX and thus make that person's day/life better. :heart:
> _The "Number 1 Rule" is to make sure your error messages
sound like they’ve been written for/by humans_.
~ _The Four H's of Writing Error Messages_
By default, Hapi does _not_ give people friendly error messages.
hapi-error is a plugin that lets your Hapi app display _consistent_, _human-friendly_ & useful
error messages so the _people_ using your app
_don't panic_.
> Try it: https://hapi-error.herokuapp.com/panacea
Under the hood, Hapi usesBoom
to handle errors. These errors are returned as JSON. e.g:
If a URL/Endpoint does not exist a 404 error is returned:
!hapi-login-404-error
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.
> Note: If you (_or anyone on your team_) are _unfamiliar_ with Hapi.js we have a
quick guide/tutorial to help get you started: https://github.com/dwyl/learn-hapi
Error handling in 3 easy steps:
``sh`
npm install hapi-error --save
Include the plugin when you register your server:
`js
var Hapi = require('@hapi/hapi');
var Path = require('path');
var server = new Hapi.Server({ 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:
module.exports = async () => {
try {
await server.register(require('hapi-error'));
await server.register(require('vision'));
server.views({
engines: {
html: require('handlebars') // or Jade or Riot or React etc.
},
path: Path.resolve(__dirname, '/your/view/directory')
});
await server.start();
return server;
} catch (e) {
throw e;
}
};
`
> See: /example/server_example.js for simple example
The default template name is error_template and is expected to exist, but can be configured in the options:
`js`
const config = {
templateName: 'my-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, Riot, React, etc. for your templates.
Your templateName (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
Each status code can be given two properties message and redirect.
The default config object for status codes:
``
const config = {
statusCodes: {
401: { message: 'Please Login to view that page' },
400: { message: 'Sorry, we do not have that page.' },
404: { message: 'Sorry, that page is not available.' }
}
};
We want to provide useful error messages that are pleasant for the user. If you think there are better defaults for messages or other codes then do let us know via issue.
Any of the above can be overwritten and new status codes can be added.
#### message Parse/replace the error message
This parameter can be of the form function(message, request) or just simply a 'string' to replace the message.
An example of a use case would be handling errors form joi validation.
Or erroring in different languages.
`js
const config = {
statusCodes: {
"401": {
"message": function(msg, req) {
var lang = findLang(req);
return translate(lang, message);
}
}
}
};
`
Or providing nice error messages like in the default config above.
#### redirect 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 (_logged in_), 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:
`js`
const config = {
statusCodes: {
"401": { // if the statusCode is 401
"redirect": "/login" // redirect to /login page/endpoint
},
"403": { // if the statusCode is 403
"redirect": function (request) {
return "/login?redirect=" + request.url.pathname
}
}
}
}
(async () => {
await server.register({
plugin: require('hapi-error'),
options: config // pass in your redirect configuration in options
});
await server.register(require('vision'));
})();
This in both cases will redirect the client/browser to the /login endpoint
and will append a query parameter with the url the person was _trying_ to visit.
With the use of function instead of simple string you can further manipulate the resulted url.
Should the parameter be a function and return false it will be ignored.
e.g: GET /admin --> 401 unauthorized --> redirect to /login?redirect=/admin
> Redirect Example: /redirect_server_example.js
Want more...? ask!
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:
`js`
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});
}
});
}request.handleError
This can be re-written (simplified) using method:
`js`
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.
}
Output:
!hapi-error-a-database-error-occured
#### Explanation:
Under the hood, request.handleError is using Hoek.assert whichassert
will 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.
> 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:
`js
var handleError = require('hapi-error').handleError;
db.get(key, function (error, result) {
handleError(error, 'Error retrieving ' + key + ' from DB :-( ');
return callback(err, result);
});
`
or in a file operation (_uploading a file to AWS S3_):
`js
var handleError = require('hapi-error').handleError;
s3Bucket.upload(params, function (err, data) {
handleError(error, 'Error retrieving ' + key + ' from DB :-( ');
return callback(err, result);
}
`
Provided the handleError is called from a function/helper
that is being _run_ by a Hapi server any errors will be _intercepted_
and _logged_ and displayed (_nicely_) to people using your app.
> Want/need to pass some more/custom data to display in your error_template view?
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:
`js`
request.handleError(!error, {errorMessage: 'Oops - there has been an error',
email: 'example@mail.co', color:'blue'});error_template.html
You will then be able to use {{email}} and {{color}} in your
As with _all_ hapi apps/APIs the recommended approach to logging
is to use good
hapi-error logs all errors using server.log (_the standard way of logging in Hapi apps_) so once you enable good in your app you will _see_ any errors in your logs.
e.g:
!hapi-error-log
If you need more debugging in your error template, hapi-error exposes _several_
useful properties which you can use.
`js`
{
"method":"GET",
"url":"/your-endpoint",
"headers":{
"authorization":"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MTIzLCJlbWFpbCI6ImhhaUBtYWlsLm1lIiwiaWF0IjoxNDc1Njc0MDQ2fQ.Xc6nCPQW4ZSf9jnIIs8wYsM4bGtvpe8peAxp6rq4y0g",
"user-agent":"shot",
"host":"http://yourserver:3001"
},
"info":{
"received":1475674046045,
"responded":0,
"remoteAddress":"127.0.0.1",
"remotePort":"",
"referrer":"",
"host":"http://yourserver:3001",
"acceptEncoding":"identity",
"hostname":"http://yourserver:3001"
},
"auth":{
"isAuthenticated":true,
"credentials":{
"id":123,
"email":"hai@mail.me",
"iat":1475674046
},
"strategy":"jwt",
"mode":"required",
"error":null,
"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MTIzLCJlbWFpbCI6ImhhaUBtYWlsLm1lIiwiaWF0IjoxNDc1Njc0MDQ2fQ.Xc6nCPQW4ZSf9jnIIs8wYsM4bGtvpe8peAxp6rq4y0g"
},
"email":"hai@mail.me",
"payload":null,
"response":{
"statusCode":500,
"error":"Internal Server Error",
"message":"An internal server error occurred"
}
}
All the properties which are logged by hapi-error are available in
your error template.
Yes! e.g: if the original url is /admin?sort=desc/login?redirect=/admin?sort=desc
the redirect url will be: /admin?sort=desc
Such that after the person has logged in they will be re-directed
back to to _as desired_.
And it's valid to have multiple question marks in the URL see:
https://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.
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:
`js`
console.log(request.response);Boom
A typical error has the format:`js``
{ [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!
+ Writing useful / friendly error messages:
https://medium.com/@thomasfuchs/how-to-write-an-error-message-883718173322