Hapi on Steroids – Using Generator Functions with Hapi

RisingStack's services:

Sign up to our newsletter!

In this article:

You may be familiar with our Koa tutorial series: we took a look on how generator functions work and how you can do a basic application using Koa.

Also, we are huge fans of hapi – if you haven’t seen our hapi node.js tutorial, now is the time!

So the question came: wouldn’t it be great if we could use generator functions with hapi?

The Motivation

Using error-first callbacks fetching users from your database may look like something like this:

Users.find({
    age: 32
}, function (err, users) {
    if (err) {
        request.server.log([
          'error',
          'users',
          err
        ]);
        return reply(boom.badImplementation());
    }

    return reply(users);
});

The same logic with hapi on steroids looks like this:

var users;

try {
  users = yield Users.find({age: 32})    
} catch (exception) {
    request.server.log([
      'error',
      'users',
      exception
    ]);
    return reply(boom.badImplementation());
}

return reply(users);

How to Use Generators With Hapi

In short: we wrapped all of the route handlers with co in hapi. It is just a thin layer, but it enables you to write all your underlying logic using generators.

var co = require('co');

function get (request, reply) {
  co(function* () {
    return yield db.getUsers();
  })
    .then(function (response) {
      reply(response);
    })
    .catch(function (err) {
      request.server.log([
        'error',
        'login',
        err
      ]);
      return reply(boom.badImplementation());
    });
}

module.exports.get = get;

Tests

You may ask: okay, okay, but how to test it, I am used to hapi.inject? Gladly the solution is pretty easy. If you are using co-mocha you can do something like this in your before hook:

before(function () {
  var _this = this;

  _this.server = yield server.create();

  this.inject = function (options) {
    return new Promise(function (resolve) {
      _this.server.inject(options, function (res) {
        return resolve(res);
      });
    });
  };
});

With this solution, a test for an endpoint will become this:

describe('GET /v1/info', function () {
  it('should return with a proper status code and body', function* () {
    var res = yield this.inject({
      method: 'GET',
      url: '/v1/info'
    });

    expect(res.result).to.eql({
      status: 'ok'
    });
  });
});

The future

It is great to see more and more ES6 features landing in either Node.js or io.js. With tools like Babel one can use an either bigger set of language features.

We are curious to hear your stories on how you use ES6 in production today, what problems you are facing – don’t hesitate to comment! 🙂

Share this post

Twitter
Facebook
LinkedIn
Reddit