29 April, 2019

Hosting a Serverless Hapi 17+ API with AWS Lambda

This is an update to my hapi-lambda package on npm and now supports Hapi 17+

npm version

tldr; A full example can be found at https://github.com/carbonrobot/hapi-lambda-demo or you can use the npm package which has more up to date code that handles headers properly. https://github.com/carbonrobot/hapi-lambda

After a couple years of using Hapi on Lambda I have come up with a better design for the hapi-lambda package and the means by which is interfaces with AWS Lambda. The original package was written prior to Lambda or Hapi having async/await support. The newer method is a lot cleaner and more extensible than the previous way of connecting the two together.

To get started, we simply write some plain old Hapi code similar to the way the Hapi tutorials are written.

// straight from the tutorials... do not use
const Hapi = require('hapi');

const init = async () => {

    const server = Hapi.server({
        port: 3000,
        host: 'localhost'
    });

    server.route({
        method: 'GET',
        path:'/',
        handler: (request, h) => 'Hello World!'
    });

    await server.start();
    console.log('Server running on %ss', server.info.uri);
};

init();

Since AWS Lambda has its own method of "listening" to requests, we dont want to start that server at the end there, but instead just return an instance of our server.

// api.js
const Hapi = require('hapi');

const init = async () => {

    const server = Hapi.server({
        port: 3000,
        host: 'localhost'
    });

    server.route({
        method: 'GET',
        path:'/',
        handler: (request, h) => 'Hello World!'
    });

    return server;
};

In the index file that we expose to Lambda, we can then wire up that server with the Lambda event handler.

const api = require('./api');

exports.handler = async (event) => {
  const server = await api.init();
  ...
};

Hapi exposes its internal operations with the inject() method on the server object. We can transform the incoming Lambda event and allow Hapi to handle the request by passing it to inject.

const api = require('./api');
const { transformRequest, transformResponse } = require('hapi-lambda');

exports.handler = async (event) => {
  const server = await api.init();

  const request = transformRequest(event);
  const response = await server.inject(request);
  return transformResponse(response);
};

To see the details of the transformation, check out the hapi-lambda repo on github. Also be sure to check out the full demo for the above code as well, it contains improvements like server instance caching for better performance and examples of how to use Hapi plugins.


Tags: , , , ,