February 01, 2018
So, you're thinking about updating your HAPI.js server to v17?
There are quite some changes with the latest HAPI.js version that are worth taking a look at. At YLD we want to be as close as possible to the latest changes and this blogpost aims to unveil a bit of the new HAPI.js version. There's some breaking changes that were created mainly due to the adoption of async/await
.
Remember async/await
(Async Functions)?
If we would have to summary async/await
in a simple sentence it would be something such as "It makes everything prettier by making async code more readable". The following example shows an asynchronous function that is waiting for a promise to be resolved:
async function getById(id) {
try {
const item = await db.getById(id)
return item
} catch (err) {
// deal with it (reject the promise)!
}
}
Small things to note:
await
always waits for a promise (in this casedb.getById
to be resolved);await
can only be used insideasync
function (hence the declarationasync function getById
);- you can use try/catch to accomplish error handling, or stick with the
then/catch
that promises provide (there's also nice alternative solutions such as apr-intercept or await-to-js); - it is not mentioned in this example, but you should be careful when calling two functions using
await
inside the sameasync
function. If you don't handle it properly you may end up with code the is running is series that could be run in parallel (checkPromise.all
for details, or even the utility apr).
But this blogpost is not about async/await
, let's go back to HAPI.js! In order to have more information about async/await
check out the ECMAScript spec or MDN.
Bye connections
Doing server.connection({ port: 3000 })
after creating your HAPI.js server is no longer required to be in a different line. All methods from server.connection
are now in server
and you can simply do:
const Hapi = require('hapi')
const server = Hapi.server({
host: 'localhost',
port: 3000,
})
Use async/await
to start your server
Forget the callback function in server.start
. Start it using async functions:
async function start() {
try {
await server.start()
} catch (err) {
console.error(err)
process.exit(1)
}
}
start()
Forget the reply
interface
That's it, reply was changed to h
which stands for hapi. Now you can just return what you want to retrieve to the client or, if your response its a bit more complex you can use h.response
.
server.route({
method: 'GET',
path: '/',
handler: (request, h) => {
return 'Hi, YLD!'
// ----> alternative:
// const response = h.response('Hi, YLD!');
// response.code(200);
// response.header('Content-Type', 'text/plain');
// return response;
},
})
s/config/options
Quick, quick one. Replace config
with options
in your routes. Everything else works as before (e.g. payload
configuration and Joi
validation), as you can follow in this code snippet:
server.route({
method: 'POST',
path: '/users',
handler: createUser,
options: {
payload: {
allow: ['application/json'],
},
validate: {
payload: {
name: Joi.string().required(),
},
},
},
})
Register a plugin
There are changes in the HAPI.js plugin signature. It now stands for an object with the following properties: { register, name, version, multiple, dependencies, once, pkg }
. Check out small example here where we create a simple yld-hapi-plugin.js
.
// inside our plugin (`yld-hapi-plugin.js`)
const plugin = {
register: (server, options) => {
server.route({
method: 'GET',
path: '/plugin',
handler: (request, h) => {
return 'Hi from a plugin'
},
})
},
name: 'yld-hapi-plugin',
version: '1.0.0',
}
module.exports = plugin
And finally register it in your server file:
// inside my HAPI.js server
const YLDHapiPlugin = require('./plugins/yld-hapi-plugin.js')
/*...*/
await server.register(YLDHapiPlugin)
server.register
now returns a promise but will still receive an array of plugins as argument, so that await server.register([Inert, Vision, YLDHapiPlugin])
is valid.
Server methods will still work as expected
Server methods are one of the most interesting features that HAPI.js has and a useful and performant way of sharing functions by attaching them to your server object rather than requiring a common module everywhere it is needed. There's no big differences here, in the following example we just call a server method add
as before, but using async/await
notation where no callback is needed as last argument when creating the method!
server.method(
'add',
(a, b) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(a + b)
}, 300)
})
},
{
cache: {
expiresIn: 60000,
generateTimeout: 1000,
},
}
)
server.route({
method: 'POST',
path: '/add',
handler: async (request, h) => {
try {
const result = await server.methods.add(1, 2)
return result
} catch (err) {
throw err
}
},
})
With async/await
comes bounce!
bounce
is a HAPI.js module that aims to make silent errors visible. Check out the following example where we use Bounce.rethrow
function. In this case b.c.d
can be a silent error (due to try/catch
& async/await
) if bounce
is not being used. Bounce allows to filter by system
and boom
errors.
server.method('add', (a, b) => {
return new Promise((resolve, reject) => {
const a = b.c.d
reject('The line before should explode: ' + a)
})
})
server.route({
method: 'POST',
path: '/add',
handler: async (request, h) => {
let result = null
try {
result = await server.methods.add(1, 2)
return result
} catch (err) {
Bounce.rethrow(err, 'system')
}
return result
},
})
That's it for now! There's some more slightly changes, but this blogpost should give you an overview and help you to start migrating to the new version. Ping us on twitter if you have some questions or want to know more.
Originally published at blog.yld.io on February 1, 2018 by Daniela Matos de Carvalho (@sericaia on Twitter/Github)