Recently I started exploring Hapi.js . I read this concept called extension points, which sounded familiar to interceptors(kinda...). So, I thought of exploring them.
Each request in hapi follows a predefined path, the request lifecycle.
Now, what is request lifecycle ?
check this out: https://hapi.dev/api/?v=19.1.1#request-lifecycle
why extension points?
- To create custom functionality along the lifecycle.
- To let us know the precise order at which our application will run.
Extension points
hapi has 7 extension points along the request lifecycle. And the order is
- onRequest
- onPreAuth
- onCredentials
- onPostAuth
- onPreHandler
- onPostHandler
- onPreResponse.
To add a function to an extension point, you call
server.ext()
Lets look at an example:
I have created a route named "/employees" where I am using a dummy api to fetch a details of employees.
'use strict';
const Hapi = require('@hapi/hapi'),
Request = require('request-promise');
const init = async () => {
const server = Hapi.server({
port: 5000,
host: 'localhost'
})
server.route({
method: 'GET',
path: '/employees',
handler: function(req, h) {
return Request('http://dummy.restapiexample.com/api/v1/employees', function (error, response, body) {
console.log('response:', response);
});
}
})
server.ext('onRequest', function(request, h){
console.log('inside onRequest');
return h.continue;
})
await server.start();
console.log('Server running at %s', server.info.port);
}
process.on('unhandledRejection', (err) => {
console.log(err);
process.exit(1);
});
init();
I am using nodemon to execute my server code
nodemon server.js
When you hit this url http://localhost:5000/employees on your browser, you would notice in your console the message within our 'onRequest' extension point prints first and then our response. This made me feel that it looks like interceptor.
You might have noticed the statement
return h.continue;
If you comment this piece of code , you will see this
Error: onRequest extension methods must return an error, a takeover response, or a continue signal
In case of interceptors, we intercept the request and do some operation and then pass on the handler so that we can complete the request. The same goes here if you noticed the error statement.
The request path and method can be modified via the request.setUrl() and request.setMethod() methods.
For example:
server.ext('onRequest', function(request, h){
console.log('inside onRequest');
request.setUrl('/');
request.setMethod('POST');
return h.continue;
})
Here, we are setting the url to '/'. so, when we try to access http://localhost:5000/employees we will be redirected to http://localhost:5000 but we must have '/' this route defined.
Next, you can change the request method from GET to POST using setMethod as we did above.
Earlier we talked about the order of lifecycle methods, so lets see when the methods are called.
server.ext('onRequest', function(request, h){
console.log('inside onRequest');
return h.continue;
});
server.ext('onPreAuth', function(request, h){
console.log('inside onPreAuth');
return h.continue;
});
server.ext('onCredentials', function(request, h){
console.log('inside onCredentials');
return h.continue;
});
server.ext('onPostAuth', function(request, h){
console.log('inside onPostAuth');
return h.continue;
});
server.ext('onPreHandler', function(request, h){
console.log('inside onPreHandler');
return h.continue;
});
server.ext('onPostHandler', function(request, h){
console.log('inside onPostHandler');
return h.continue;
});
server.ext('onPreResponse', function(request, h){
console.log('inside onPreResponse');
return h.continue;
});
Here, we have added all the extension points in the order as mentioned the official documentation and have placed a log statement inside each extension point implementation.
Now, we need to access http://localhost:5000/employees to see what happens
If you check your console, you would see this order:
Server running at 5000
inside onRequest
inside onPreAuth
inside onPostAuth
inside onPreHandler
response: IncomingMessage{
//entire response body
}
inside onPostHandler
inside onPreResponse
Things to notice:
onPreAuth and onPostAuth are called regardless if authentication is performed.
But, onCredentials will be called only if authentication is performed and that is the reason we didn't see the log statement which we defined inside onCredentials.
I hope this helps you understand the flow of extension points and when to use them.
Thanks for reading this. Cheers !!!
Top comments (2)
Thanks for your post, pretty interesting! I came from KoaJS and I think this could be similar to middlewares in some way, for example if you want to implement some custom error handling to provide a graceful error response to the client. Do you know if it is possible to modify the response using a lifecycle interceptor?
Thanks for this post!
Do you know if there is any way to pass data between the methods above e.g. create an object on
onPreAuth
that would be available the way toonPostHandler
?