Categories
Hapi

Server-Side Development with Hapi.js — Sessions and Advanced HTTP Requests

Hapi.js is a small Node framework for developing back end web apps.

In this article, we’ll look at how to create back end apps with Hapi.js.

Advanced HTTP Requests

We can make complex HTTP requests with the @hapi/wreck module.

For example, we can write:

const Wreck = require('@hapi/wreck');
const Https = require('https')
const Http = require('http')

const method = 'GET';
const uri = '/';
const readableStream = Wreck.toReadableStream('foo=bar');

const wreck = Wreck.defaults({
  headers: { 'x-foo-bar': 123 },
  agents: {
    https: new Https.Agent({ maxSockets: 100 }),
    http: new Http.Agent({ maxSockets: 1000 }),
    httpsAllowUnauthorized: new Https.Agent({ maxSockets: 100, rejectUnauthorized: false })
  }
});

const wreckWithTimeout = wreck.defaults({
  timeout: 5
});

const options = {
  baseUrl: 'https://www.example.com',
  payload: readableStream || 'foo=bar' || Buffer.from('foo=bar'),
  headers: {},
  redirects: 3,
  beforeRedirect: (redirectMethod, statusCode, location, resHeaders, redirectOptions, next) => next(),
  redirected: function (statusCode, location, req) {},
  timeout: 1000,
  maxBytes: 1048576,
  rejectUnauthorized: false,
  agent: null,
};

const example = async () => {
  const promise = wreck.request(method, uri, options);
  try {
    const res = await promise;
    const body = await Wreck.read(res, options);
    console.log(body.toString());
  }
  catch (err) {
    console.log(err)
  }
};

example()

The headers property has the request headers.

timeout has the request timeout in milliseconds.

maxBytes has the max size of a request.

agent has the user agent.

We can add the secureProtocol is the SSL method to use.

And the ciphers property can be added to choose the TLS cipher.

Then we use Wreck.read to read the response.

We can also call promise.req.abort() to abort the request.

Session Handling

We can handle session data with the @hapi/yar module.

For instance, we can write:

const Hapi = require('@hapi/hapi');

const server = Hapi.Server({ port: 3000 });
const options = {
  storeBlank: false,
  cookieOptions: {
    password: 'the-password-must-be-at-least-32-characters-long'
  }
};

const init = async () => {
  await server.register({
    plugin: require('@hapi/yar'),
    options
  });

  server.route({
    method: 'GET',
    path: '/',
    handler: (request, h) => {
      request.yar.set('example', { key: 'value' });
      return 'hello';
    }
  });

  server.route({
    method: 'GET',
    path: '/session',
    handler: (request, h) => {
      const example = request.yar.get('example');
      return example;
    }
  });

  await server.start();
  console.log('Server running at:', server.info.uri);
};
init();

to register the @hapi/yar plugin with:

await server.register({
  plugin: require('@hapi/yar'),
  options
});

The options property has the cookieOptions.password property to set the password for encrypting the session.

Then in the / route, we call request.yar.set method to add the key and value to the session.

And then in the /session route, we call request.yar.get method with the key we want to get to get the data.

We should get:

{
  "key": "value"
}

as the response.

Conclusion

We can use the @hapi/wreck module to make advanced HTTP requests.

And the @hapi/yar module lets us manage session data across our app.

Categories
Hapi

Server-Side Development with Hapi.js — Rendering Templates and HTTP Requests

Hapi.js is a small Node framework for developing back end web apps.

In this article, we’ll look at how to create back end apps with Hapi.js.

Rendering Templates

We can render Nunjucks templates in our Hapi app with the @hapi/vision plugin.

To do this, we write:

index.js

const Nunjucks = require('nunjucks');
const Hapi = require('@hapi/hapi');
const Vision = require('@hapi/vision');

const server = Hapi.Server({ port: 3000 });

const rootHandler = (request, h) => {
  return h.view('index', {
    title: request.server.version,
    message: 'Hello!'
  });
};

const init = async () => {
  await server.register(Vision);

server.views({
    engines: {
      html: {
        compile: (src, options) => {
          const template = Nunjucks.compile(src, options.environment);
          return (context) => {
            return template.render(context);
          };
        },
        prepare: (options, next) => {
          options.compileOptions.environment = Nunjucks.configure(options.path, { watch : false });
          return next();
        }
      }
    },
    relativeTo: __dirname,
    path: 'templates'
  });

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

await server.start();
  console.log('Server running at:', server.info.uri);
};

init();

templates/index.html

{{message}}

We require the Nunjucks module.

Then we call Nunjucks.compile to with the src parameter to compile the source to the template.

Then we return a function that calls template.render with the context to render the template.

The context has the 2nd argument of h.view as its value.

The prepare method is called before the compile method and it sets the environment option set environment-specific settings.

index.html has the template content.

@hapi/vision also supports render Twig templates.

To do this, we write:

const Twig = require('twig');
const Hapi = require('@hapi/hapi');
const Vision = require('@hapi/vision');

const server = Hapi.Server({ port: 3000 });

const rootHandler = (request, h) => {
  return h.view('index', {
    title: request.server.version,
    message: 'Hello!'
  });
};

const init = async () => {
  await server.register(Vision);

  server.views({
    engines: {
      twig: {
        compile: (src, options) => {
          const template = Twig.twig({ id: options.filename, data: src });
          return (context) => {
            return template.render(context);
          };
        }
      }
    },
    relativeTo: __dirname,
    path: 'templates'
  });

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

  await server.start();
  console.log('Server running at:', server.info.uri);
};

init();

templates/index.twig

{{ message }}

We set the engines.twig.compile property to a function.

In it, we call Twig.twig to set the id and data of the template.

And we return a function that takes the context parameter and call template.render on it.

context has the object that we pass in as the 2nd argument.

index.twig is our Twig template.

Making HTTP Requests

We can make HTTP requests in our Node app with the @hapi/wreck module.

For instance, we can write:

const Wreck = require('@hapi/wreck');

const makeRequest = async () => {
  const { res, payload } = await Wreck.get('http://example.com');
  console.log(payload.toString());
};

try {
  makeRequest();
}
catch (ex) {
  console.error(ex);
}

We require the @hapi/wreck module.

Then we call Wreck.get to make a GET request.

Then we get the response with the payload property.

And we convert that to a string with the toString method.

Conclusion

We can render various kinds of templates with the @hapi/vision module.

Also, we can make HTTP requests with the @hapi/wreck module in our Node app.

Categories
Hapi

Server-Side Development with Hapi.js — Rendering Templates

Hapi.js is a small Node framework for developing back end web apps.

In this article, we’ll look at how to create back end apps with Hapi.js.

Rendering Templates

We can render templates with the @hapi/vision module.

For instance, we can write:

index.js

const Ejs = require('ejs');
const Hapi = require('@hapi/hapi');
const Vision = require('@hapi/vision');

const server = Hapi.Server({ port: 3000 });

const rootHandler = (request, h) => {
  return h.view('index', {
    title: request.server.version,
    message: 'Hello Ejs!'
  });
};

const init = async () => {
  await server.register(Vision);

  server.views({
    engines: { ejs: Ejs },
    relativeTo: __dirname,
    path: 'templates'
  });

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

  await server.start();
  console.log('Server running at:', server.info.uri);
};

init();

templates/index.ejs

<%= message %>

We have await server.register(Vision); to register the @hapi/vision plugin.

Then we call h.view to render the view.

title and message will be available in the template.

We set the engines.ejs property to the Ejs module to use it as our rendering engine.

We can use the Handlebars view engine by writing:

index.js

const Handlebars = require('handlebars');
const Hapi = require('@hapi/hapi');
const Vision = require('@hapi/vision');

const server = Hapi.Server({ port: 3000 });

const rootHandler = (request, h) => {
  return h.view('index', {
    title: request.server.version,
    message: 'Hello!'
  });
};

const init = async () => {
  await server.register(Vision);

  server.views({
    engines: { html: Handlebars },
    relativeTo: __dirname,
    path: 'templates'
  });

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

  await server.start();
  console.log('Server running at:', server.info.uri);
};

init();

templates/index.html

{{message}}

We change the engines property to { html: Handlebars } and require the handlebars module to use the Handlebars views engine.

To use the Pug view engine with Hapi, we write:

index.js

const Pug = require('pug');
const Hapi = require('@hapi/hapi');
const Vision = require('@hapi/vision');

const server = Hapi.Server({ port: 3000 });

const rootHandler = (request, h) => {
  return h.view('index', {
    title: request.server.version,
    message: 'Hello!'
  });
};

const init = async () => {
  await server.register(Vision);

  server.views({
    engines: { pug: Pug },
    relativeTo: __dirname,
    path: 'templates'
  });

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

  await server.start();
  console.log('Server running at:', server.info.uri);
};

init();

templates/index.pug

p #{message}

To use the Mustache view engine, we write:

const Mustache = require('mustache');
const Hapi = require('@hapi/hapi');
const Vision = require('@hapi/vision');

const server = Hapi.Server({ port: 3000 });

const rootHandler = (request, h) => {
  return h.view('index', {
    title: request.server.version,
    message: 'Hello!'
  });
};

const init = async () => {
  await server.register(Vision);

  server.views({
    engines: {
      html: {
        compile: (template) => {
          Mustache.parse(template);
          return (context) => {
            return Mustache.render(template, context);
          };
        }
      }
    },
    relativeTo: __dirname,
    path: 'templates'
  });

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

  await server.start();
  console.log('Server running at:', server.info.uri);
};

init();

templates/index.html :

{{message}}

We have the engines.html.compile method to compile the template into rendered HTML.

context has the data we pass into the 2nd argument of h.view .

Conclusion

We can render various kinds of templates into HTML with the @hapi/vision plugin.

Categories
Hapi

Server-Side Development with Hapi.js — User-Agent, Payload, Semantic Version, and Sorting

Hapi.js is a small Node framework for developing back end web apps.

In this article, we’ll look at how to create back end apps with Hapi.js.

Get User Agent Info

We can get user agent info from the request with the @hapi/scooter module.

To use it, we write:

const Hapi = require('@hapi/hapi');
const Scooter = require('@hapi/scooter');

const init = async () => {
  const server = new Hapi.Server({
    port: 3000,
    host: '0.0.0.0'
  });

  server.route({
    method: 'GET',
    path: '/',
    config: {
      handler(request, h) {
        return request.plugins.scooter;
      },
    }
  });
  await server.register(Scooter);
  await server.start();
  console.log('Server running at:', server.info.uri);
};

process.on('unhandledRejection', (err) => {
  console.log(err);
  process.exit(1);
});

init();

We have await server.register(Scooter); to register the Scooter plugin.

And we use request.plugins.scooter to return the user agent object.

Semantic Versioning

We can use the @hapi/somever module to work with semantic version numbers.

For example, we can write:

const Hapi = require('@hapi/hapi');
const somever = require('@hapi/somever');

const init = async () => {
  const server = new Hapi.Server({
    port: 3000,
    host: '0.0.0.0'
  });

  server.route({
    method: 'GET',
    path: '/',
    config: {
      handler(request, h) {
        return somever.compare('1.2.3', '1.2.5');
      },
    }
  });
  await server.start();
  console.log('Server running at:', server.info.uri);
};

process.on('unhandledRejection', (err) => {
  console.log(err);
  process.exit(1);
});

init();

to compare 2 version numbers with the somever.compare method.

Then we get -1 returned since the first version number is earlier than the 2nd one.

Also, we can compare versions with the match method:

const Hapi = require('@hapi/hapi');
const somever = require('@hapi/somever');

const init = async () => {
  const server = new Hapi.Server({
    port: 3000,
    host: '0.0.0.0'
  });

  server.route({
    method: 'GET',
    path: '/',
    config: {
      handler(request, h) {
        return somever.match('1.2.x', '1.2.5');
      },
    }
  });
  await server.start();
  console.log('Server running at:', server.info.uri);
};

process.on('unhandledRejection', (err) => {
  console.log(err);
  process.exit(1);
});

init();

And we get true since '1.2.x' includes '1.2.5' .

Parsing HTTP Payloads

We can parse HTTP payload with the @hapi/subtext module.

For example, we can write:

const Http = require('http');
const Subtext = require('@hapi/subtext');

Http.createServer(async (request, response) => {
  const { payload, mime } = await Subtext.parse(request, null, { parse: true, output: 'data' });
  response.writeHead(200, { 'Content-Type': 'text/plain' });
  response.end(`Payload contains: ${JSON.stringify(payload)}`);

}).listen(1337, '0.0.0.0');

console.log('Server running/');

We call Subtext.parse with the request object to parse it.

It returns a promise with an object with the payload and mime properties.

payload has the payload sent.

mime has the MIME type.

Sorting Text

We can sort text with the @hapi/topo module.

To use it, we write:

const Hapi = require('@hapi/hapi');
const Topo = require('@hapi/topo');

const morning = new Topo.Sorter();

morning.add('Nap', { after: ['breakfast', 'prep'] });
morning.add([
  'Make toast',
  'Pour juice'
], { before: 'breakfast', group: 'prep' });
morning.add('Eat breakfast', { group: 'breakfast' });

const init = async () => {
  const server = new Hapi.Server({
    port: 3000,
    host: '0.0.0.0'
  });
  server.route({
    method: 'GET',
    path: '/',
    config: {
      handler(request, h) {
        return morning.nodes;
      },
    }
  });
  await server.start();
  console.log('Server running at:', server.info.uri);
};
process.on('unhandledRejection', (err) => {
  console.log(err);
  process.exit(1);
});
init();

We create the morning object with the Topo.Sorter constructor.

Then we call morning.add to add the items we want to sort.

The after property specifies the items that come after the item in the first argument.

group lets us add items into groups.

Then we can get the sorted items with the morning.nodes property.

Conclusion

We can sort items with the @hapi/topo module.

The @hapi/subtext module lets us parse request payloads.

And @hapi/scooter returns user agent info.

@hapi/somever lets us compare semantic version strings.

Categories
Hapi

Server-Side Development with Hapi.js — MIME Types and Events

Hapi.js is a small Node framework for developing back end web apps.

In this article, we’ll look at how to create back end apps with Hapi.js.

MIME Types

We can get information about various MIME types with the modules.

For instance, we can write:

const Hapi = require('@hapi/hapi');
const Mimos = require('@hapi/mimos');

const options = {
  override: {
    'node/module': {
      source: 'iana',
      compressible: true,
      extensions: ['node', 'module', 'npm'],
      type: 'node/module'
    },
    'application/javascript': {
      source: 'iana',
      charset: 'UTF-8',
      compressible: true,
      extensions: ['js', 'javascript'],
      type: 'text/javascript'
    },
    'text/html': {
      predicate(mime) {
        mime.foo = 'test';
        return mime;
      }
    }
  }
}

const mimos = new Mimos(options);

const init = async () => {
  const server = new Hapi.Server({
    port: 3000,
    host: '0.0.0.0'
  });

  server.route({
    method: 'GET',
    path: '/',
    config: {
      handler(request, h) {
        return mimos.type('text/html');
      },
    }
  });

  await server.start();
  console.log('Server running at:', server.info.uri);
};

process.on('unhandledRejection', (err) => {
  console.log(err);
  process.exit(1);
});
init();

We add the options object with the override property to set the data for the given MIME types.

Then we call mimos.type with the MIME type we want to look up.

And then we get:

{"source":"iana","compressible":true,"extensions":["html","htm","shtml"],"type":"text/html","foo":"test"}

as the returned value.

Collect Server Ops Data

We can collect server ops data easily with the @hapi/oppsy module.

To use it, we can write:

const Hapi = require('@hapi/hapi');
const Oppsy = require('@hapi/oppsy');

const init = async () => {
  const server = new Hapi.Server({
    port: 3000,
    host: '0.0.0.0'
  });

  const oppsy = new Oppsy(server);
  oppsy.on('ops', (data) => {
    console.log(data);
  });

  server.route({
    method: 'GET',
    path: '/',
    config: {
      handler(request, h) {
        return 'hello'
      },
    }
  });

  await server.start();
  oppsy.start(1000);
  console.log('Server running at:', server.info.uri);
};

process.on('unhandledRejection', (err) => {
  console.log(err);
  process.exit(1);
});
init();

We create the oppsy object with the Oppsy constructor.

Then we listen to the ops event with the oppsy.on method.

And in the event handler, we log the data from the server.

It includes data like requests, CPU usage, response times, memory usage, and more.

Events

We can create our own event bus with the @hapi/podium module.

For example, we can write:

const Hapi = require('@hapi/hapi');
const Podium = require('@hapi/podium');

const emitter = new Podium()
const context = { count: 0 }

emitter.registerEvent({
  name: 'event',
  channels: ['ch1', 'ch2']
})

const handler1 = function () {
  ++this.count
  console.log(this.count)
};

const handler2 = function () {
  this.count = this.count + 2
  console.log(this.count)
}

emitter.on({
  name: 'event',
  channels: ['ch1']
}, handler1, context);

emitter.on({
  name: 'event',
  channels: ['ch2']
}, handler2, context)

emitter.emit({
  name: 'event',
  channel: 'ch1'
})

emitter.emit({
  name: 'event',
  channel: 'ch2'
})

emitter.hasListeners('event')
emitter.removeAllListeners('event')
const init = async () => {
  const server = new Hapi.Server({
    port: 3000,
    host: '0.0.0.0'
  });

  server.route({
    method: 'GET',
    path: '/',
    config: {
      handler(request, h) {
        return 'hello'
      },
    }
  });

  await server.start();
  console.log('Server running at:', server.info.uri);
};

process.on('unhandledRejection', (err) => {
  console.log(err);
  process.exit(1);
});
init();

We import the module, then we create the emitter object with it.

Then we register events with the emitter.registerEvent method.

We can separate events into their own channels.

context is the value of this in the event handlers.

So this.count will be updated and logged within the event handlers.

Conclusion

We can create our own event bus with @hapi/podium and handle MIME types with the @hapi/mimos module.