HapiJS: A delightful experience

HapiJS: A delightful experience

This talk was given at the MVD-JS (Montevideo JavaScript meetup) by Matias Olivera (@moliveraf) and me (@espinosacurbelo).

Thanks to this amazing people:
- Eran Hammer (@eranhammer) - Hapi creator (http://hueniverse.com/2012/12/20/hapi-a-prologue/ & http://hueniverse.com/2014/08/20/performance-at-rest/)
- Dave Stevens (@shakefon) - Organise the project into plugins (https://medium.com/nerds-zappos/manifests-plugins-and-schemas-organizing-your-hapi-application-68cf316730ef)

71f946b4fdeb8a14e43297b2708e4c3a?s=128

Rodrigo Espinosa Curbelo

February 04, 2015
Tweet

Transcript

  1. 4.

    –Eran Hammer (hapi creator) “hapi was created around the idea

    that configuration is better than code, that business logic must be isolated from the transport layer...”
  2. 6.

    Create a new server object 1 var Hapi = require('hapi');

    2 3 var server = new Hapi.Server();
  3. 7.

    Then, define a connection 1 var Hapi = require('hapi'); 2

    3 var server = new Hapi.Server(); 4 server.connection({ port: 3000 });
  4. 8.

    Specifying the port in the connection 1 var Hapi =

    require('hapi'); 2 3 var server = new Hapi.Server(); 4 server.connection({ port: 3000 });
  5. 9.

    Starting the server 1 var Hapi = require('hapi'); 2 3

    var server = new Hapi.Server(); 4 server.connection({ port: 3000 }); 5 6 server.start(function () { 7 8 console.log('Server running at:', server.info.uri); 9 });
  6. 11.

    Adding routes 1 var Hapi = require('hapi'); 2 3 var

    server = new Hapi.Server(); 4 server.connection({ port: 3000 }); 5 6 server.route({ 7 method: 'GET', 8 path: '/', 9 handler: function (request, reply) { 10 reply('Hello, world!'); 11 } 12 }); 13 14 server.start(function () { 15 16 console.log('Server running at:', server.info.uri); 17 });
  7. 12.

    server.route takes a single configuration object { method: 'GET', path:

    '/', handler: function (request, reply) { reply('Hello, world!'); } }
  8. 13.

    The HTTP verb you're responding to doesn't dictate the API

    method to use unlike express app.get(), app.post(), etc.
  9. 14.

    Response with the reply function 1 var handler = function

    (request, reply) { 2 3 return reply('success'); 4 };
  10. 16.

    Input validation 1 server.route({ 2 method: 'GET', 3 path: '/hello/{name}',

    4 handler: function (request, reply) { 5 reply('Hello ' + request.params.name + '!'); 6 }, 7 config: { 8 validate: { 9 params: { 10 name: Joi.string().min(3).max(10) 11 } 12 } 13 } 14 });
  11. 17.

    Input validation response for /hello/a { "error": "Bad Request", "message":

    "the length of name must be at least 3 characters long", "statusCode": 400, "validation": { "keys": [ "name" ], "source": "params" } }
  12. 18.

    Query validation 1 server.route({ 2 method: 'GET', 3 path: '/list',

    4 handler: function (request, reply) { 5 reply(resources.slice(0, request.query.limit); 6 }, 7 config: { 8 validate: { 9 query: { 10 limit: Joi.number().integer().max(100) 11 } 12 } 13 } 14 });
  13. 19.

    Query validation response for /list?limit=15&offset=15 { "error": "Bad Request", "message":

    "the key offset is not allowed", "statusCode": 400, "validation": { "keys": [ "offset" ], "source": "query" } }
  14. 22.

    You may also validate the payload data sent to a

    route, via the validate.payload parameter
  15. 25.

    Simple plugin 1 var myPlugin = { 2 register: function

    (server, options, next) { 3 next(); 4 } 5 } 6 7 myPlugin.register.attributes = { 8 name: 'myPlugin', 9 version: '1.0.0' 10 };
  16. 26.

    As a external module 1 exports.register = function (server, options,

    next) { 2 next(); 3 }; 4 5 exports.register.attributes = { 6 pkg: require('./package.json') 7 };
  17. 27.

    Loading a plugin 1 server.register({register: require(‘myplugin')}, 2 function (err) {

    3 if (err) { 4 console.error('Failed to load plugin:’, 5 err); 6 } 7 });
  18. 28.

    Loading multiple plugins 1 server.register([ 2 { 3 register: require('myplugin'),

    4 options: {} // options for 'myplugin' 5 },{ 6 register: require('yourplugin'), 7 options: {} // options for 'yourplugin' 8 } 9 ], function (err) { 10 if (err) { 11 console.error('Failed to load a plugin:', err); 12 } 13 });
  19. 47.

    Creating a server 1 var Hapi = require('hapi'); 2 3

    var server = new Hapi.Server(); 4 server.connection({ port: 3000 }); 5 6 server.start(function () { 7 8 console.log('Server running at:', server.info.uri); 9 });
  20. 48.

    Composing a server 1 var Glue = require('glue'); 2 var

    manifest = { 3 connections: [{ 4 port: 80 5 }] 6 }; 7 8 Glue.compose(manifest, function (err, server) { 9 10 server.start(function (err) { 11 12 console.log('Server running at:’, 13 server.info.uri); 14 }); 15 });
  21. 49.

    manifest.json { "server": { "app": { "slogan": "We push the

    web forward" } }, "connections": [ { "port": 80, "labels": ["web-ui"] }, { "port": 9000, "labels": ["api"] } ] }
  22. 50.

    Composing a server 1 var Glue = require('glue'); 2 3

    Glue.compose(require(‘./config/manifest.json'), 4 function (err, server) { 5 6 server.start(function (err) { 7 8 // UI running on port 80, 9 // API running on port 9000 10 }); 11 });
  23. 52.

    Organising into Controllers / |_ index.js |_ lib |_ controllers

    |_ about.js |_ home.js |_ search.js |_ views |_ about.html |_ home.html |_ layout.html |_ search.html
  24. 53.

    lib/controllers/home.js 1 module.exports = function (request, reply) { 2 3

    var context = { 4 pageTitle: 'Home Page' 5 }; 6 7 reply.view('home', context); 8 };
  25. 54.

    index.js 1 server.route({ 2 path: '/', 3 method: 'GET', 4

    handler: require('./lib/controllers/home') 5 });
  26. 57.

    Organising into Plugins / |_ index.js |_ lib |_ modules

    |_ company |_ about.js |_ home.js |_ index.js |_ package.json |_ search |_ index.js |_ package.json |_ search.js |_ templates |_ about.html |_ home.html |_ layout.html |_ search.html
  27. 58.

    lib/modules/company/ index.js 1 exports.register = function (server, options, next) {

    2 3 server.route({ 4 path: '/', 5 method: 'GET', 6 handler: require('./home') 7 }); 8 9 server.route({ 10 path: '/about', 11 method: 'GET', 12 handler: require('./about') 13 }); 14 15 next(); 16 }; 17 18 exports.register.attributes = { 19 pkg: require('./package.json') 20 };
  28. 60.

    lib/modules/company/ home.js 1 module.exports = function (request, reply) { 2

    3 var context = { 4 pageTitle: 'Home Page' 5 }; 6 7 reply.view('home', context); 8 };
  29. 61.

    manifest.json { "server": { "app": { "slogan": "We push the

    web forward" } }, "connections": [ { "port": 80, "labels": ["web-ui"] }, { "port": 9000, "labels": ["api"] } ], "plugins": { "./company": null, "./search": null } }
  30. 62.

    index.js 1 var Glue = require('glue'); 2 var manifest =

    require('./config/manifest.json'); 3 var options = { 4 relativeTo: process.cwd() + '/lib/modules' 5 }; 6 7 Glue.compose(manifest, options, function (err, server) { 8 9 server.start(function (err) { 10 11 // UI running on port 80, 12 // API running on port 9000 13 }); 14 });
  31. 63.