hapi –



hapi –

0 0


nashjs_hapi_presentation

Slides for my lighting talk about hapi.js and some of it's compelling features at NashJS

On Github kevinold / nashjs_hapi_presentation

hapi

A rich framework for building applications and services

What is hapi?

  • Built by WalmartLabs
  • For web apps and REST API's on Node
  • Based on Express and Director

Http API

hapi enables developers to focus on writing reusable application logic instead of spending time building infrastructure.
  • Http API

Node Black Friday

Why another framework?

  • Config vs Code
  • Node objects not modified
  • Ran into issues with multiple teams building functionality at the same time
  • Plugin ("App", "Server Partial") architecture *confusing*
  • (e.g. Wishlist, Checkout, My Account, etc.)

  • Configuration best seen at the route level (Param validation via Joi, Prereqs, Cookie, Cache)
  • Express and Director modify the Node/Connect objects themselves directly and are dependent upon that API
  • "Plugin" is not the best word to describe it
  • Allows you to build your app in pluggable components
  • Great for large teams (e.g. Wishlist, Checkout, My Account, etc.)

Modules

  • joi - validation
  • travelouge - Passport.js integration
  • iron - encryption
  • lout - API doc generator
  • good - process monitoring (logs)
  • catbox - object caching (redis, riak, etc)
  • boom - friendly error objects
  • bassmaster - batch requests

Simple server

var Hapi = require('hapi');

// Create a server with a host and port
var server = Hapi.createServer('localhost', 8000);


// Add the route(s)
server.route({
  method: 'GET',
  path: '/hello',
  handler: function (request, reply) {
    reply('hello world');
  }
});


// Start the server
server.start();
console.log(server.table());  // routing table
Sample server with a /hello endpoint from hapijs.com
  • Note that server.route accepts a hash of routes.

Validation with Joi

var Joi = require("joi");

server.route({
  method: 'GET',
  path: '/hello-world',
  validate: {
      params: {
          name: Joi.string().min(8).max(100)
      },
      query: {
          mood: Joi.string().valid(["neutral","happy","sad"])
                            .default("neutral")
      }
      // headers and payload too!
  },
  handler: function (request, reply) { reply('hello world'); }
});

Vhost

server.route({
  method: 'GET',
  vhost: 'example.com',
  path: '/hello',
  handler: function (request, reply) {
    reply('hello world');
  }
});

Caching headers

server.route({
  method: 'GET',
  path: '/hello',
  config: {
    handler: function (request, reply) {
      reply('hello world');
    },
    cache: { expiresIn: 60 * 5 * 1000 } // cache-control: max-age=300, must-revalidate 
  }
});

API doc generation - lout

// server code here
server.pack.require('lout', function() {
    server.start();
});

Errors - boom

var Hapi = require('hapi');
Hapi.error.badRequest('Invalid parameter value');
Hapi.error.unauthorized('Stale timestamp', 'Hawk', { ts: fresh, tsm: tsm });
Hapi.error.forbidden('Missing permissions');
Hapi.error.notFound('Wrong number');

Views / Templating Engines

server.views({
    engines: {
        html: 'handlebars',
        jade: 'jade'
    },
    path: '/templates'
});

var handler = function (request, reply) {

    var context = {
        title: 'Views Example',
        message: 'Hello, World'
    };
    reply.view('hello', context);
}

Simple file serving

server.route({
  method: 'GET',
  path: '/favicon.ico',
  handler: {
    file: 'favicon.ico'
  }
});

Flow control - .hold() && .send()

var handler = function (request, reply) {

    var response = reply('success').hold();

    // lookup user id, etc.

    onTimeout(function () {

        response.send();
    }, 1000);
};

Encrypted cookie - via iron

server.state('visitor', {
  ttl: 250000,
  encoding: 'iron',
  password: 'super-secret'
});

var handler = function (request, reply) {
  reply.view('home').state('visitor', { foo: true }); // object in cookie
};

Stale cache checking (server.cache)

var cache = server.cache('countries', 
{ 
  expiresIn: 60 * 1000,  // expires in 1 min
  staleIn: 45 * 1000,    // stale in 45 seconds
  staleTimeout: 300 // wait before checking if an item is stale
});

Route prerequisites

var Hapi = require('hapi');
var server = new Hapi.Server();

var pre1 = function (request, reply) { reply('Hello'); };

var pre2 = function (request, reply) { reply('World'); };

var pre3 = function (request, reply) { 
    reply(request.pre.m1 + ' ' + request.pre.m2);
};

server.route({
    method: 'GET',
    path: '/',
    config: {
        pre: [
            [
                // m1 and m2 executed in parallel
                { method: pre1, assign: 'm1' },
                { method: pre2, assign: 'm2' }
            ],
            { method: pre3, assign: 'm3' },
        ],
        handler: function (request, reply) {

            reply(request.pre.m3 + '\n');
        }
    }
});

process monitoring - good

  • Broadcast logs to remote endpoint (http, udp, redis)
  • Replaying log requests - via "replay"

process monitoring - good

var Hapi = require('hapi');

var server = new Hapi.Server();

var options = {
    subscribers: {
        'console':                         ['ops', 'request', 'log', 'error'],
        'http://localhost/logs':           ['log'],
        '/tmp/logs/':                      ['request', 'log'],
        'udp://127.0.0.1:9000':            ['request'],
        'redis://127.0.0.1:6379/listname': ['request']
    }
};

server.pack.require('good', options, function (err) {
    if (!err) {
        // Plugin loaded successfully
    }
});

Also included...

  • Transport encoding
  • Directory listing / serving
  • Proxying
  • JSONP support on routes
  • Control over payload parsing
  • Server methods (were Helpers) (server.method)
  • Cookies support (Encrypted, Base64, Base64 + JSON) (server.state)

Last thoughts...

  • GREAT docs
  • Plugins can be used elsewhere (iron, joi, etc)
  • hapijs.com released this week
  • npm just announce rewrite with hapi
  • Development moves VERY fast
  • PR's accepted; Community involvement highly encouraged
  • Yes, it scales ;)

Resources

Thanks!

@kevinold

kevin@kevinold.com