hapi — Create and Use Custom Handlebars Helpers

Hapi has great view support out of the box and ships with handlebars support by default. The framework offers multiple options to configure handlebars the way it fits perfectly into your project structure. Within earlier posts, we already guided you through the creation of a dynamic layout template and partial view support. This time, we use the helpersPath option and create our own custom handlebars helpers for views rendered by hapi.

hapi Series Overview

File Structure

If you read the related posts, you will recognize the file structure used for this tutorial since we already used it for the previous ones. We place all our template files (ending on .html) into the views directory. Additionally, to keep the helpers close to the views, we put them in a separated helpers folder inside the views directory.

Besides the views, we have a single file server.js which describes the minimum functionality required to make this example work properly.

The following overview outlines the the file structure:

views/  
    helpers/
        isSparta.js
        isAdmin.js
    partials/
    layout/
        default.html
    index.html
server.js  

Our helper files have their own directory called helpers and are javascript files ending on .js. The default layout is located within the layout folder. The index.html is a normal view showing information to the user :)

Hapi Server View Configuration

As with dynamic layouts and partial views, we use the same server code for this example about custom helpers with hapi. The important part is the view configuration for the server. This time, we define the helpersPath property to let hapi know where to find our custom handlebars helpers.

const Hapi = require('hapi')

// Create hapi server instance
const server = new Hapi.Server({  
    host: 'localhost',
    port: 3000
})


async function liftOff() {  
  await server.register({
    plugin: require('vision')  // add template rendering support in hapi
  })

  server.views({
    engines: {
        html: require('handlebars')
    },
    path: 'views',
    layoutPath: 'views/layout',
    layout: 'default',
    partialsPath: 'views/partials'
    helpersPath: 'views/helpers',
  })

  // create your routes
  server.route({
    method: 'GET',
    path: '/',
    handler: (request, h) => {
      // Render the view with the custom greeting
      const data = {
        title: 'This is Index!',
        message: 'Hello, World. You crazy handlebars layout',
        user: { role: 'user' }
      }

      return h.view('index', data)
    }
  })

  try {
    await server.start()
    // Log to the console the host and port info
    console.log('Server started at: ' + server.info.uri)
  } catch (err) {
    console.log(err)
    process.exit(1)
  }
}

liftOff()  

The helpersPath property expects the directory path to your helper files. Hapi does not support sub-folders within the helpers directory. That’s why you need to put all your helpers into the defined directory.

Note: keep an eye on the path definition. The example above uses a relative path to the helper folder instead of an absolute path. Starting the node server from another location than the root folder of this example project will result in wrong paths. Node offers options like __dirname and the path module to create more robust paths.

Create Your Helper

Before we create a custom helper, we need to set you in the right context. The important thing to understand: the helpers we’re going to create extend the handlebars template engine, not hapi itself. Helpers can only be used in handlebars view templates, not as functions in your hapi server code.

Helpers are functions used within view templates. They can perform data manipulations and transformations using the template context. The name of each file within the helpers directory is used as the helper name.

Helpers must export a single method with signature function(context) and return a string value. Passing data to a helper extends the signature to function(options, context). We explain both method declarations in more detail in the following subsections.

Helper Definition: function(context)

Hapi expects a single function with signature function(context) for each helper. The following code snippet illustrates the isAdmin helper function which can be used to check whether a user has an admin role assigned.

// isAdmin Helper
// Usage: `{{#isAdmin}}`
// Checks whether the user has admin or owner role
const _ = require('lodash')

function isAdmin(context) {  
  if (_.isEmpty(context)) {
    return context.inverse(this)
  }

  const user = context.data.root.user

  if (_.includes([ user.role ], 'owner', 'admin')) {
    return context.fn(this)
  }

  return context.inverse(this)
}

module.exports = isAdmin  

Data passed to a view in hapi is available within the helper’s context object. Hapi saves the data in the context.data.root object. Let’s dive into the code and have a look at each part.

Actually, the context object shouldn’t be empty, null or undefined. We just make sure you won’t run into type errors because of undefined is not a function. The user object within this helper is available because we passed data to the view within the handler method of the route definition. Further, we use lodash to verify whether the user has the admin or owner role assigned.

If the user is verified as admin or owner, we call return context.fn(this). The method encapsulates the view content while calling the helper. Use the isAdmin helper in your view with block definition and add further HTML markup which will be rendered if the helper returns true. The usage section provides a concrete example.

If the user doesn’t have admin access, the helper returns with return context.inverse(this). Your view will be transformed and the part between your isAdmin block won’t be rendered.

Helper Options: function(options, context)

Handlebars offers the ability to pass additional options to your helper function. Hapi supports this functionality as well and passes additional options from your view to your helper. The following code example describes the usage of options within your helper function.

// isSparta helper — for demonstration purposes
var _               = require('lodash'),  
    isSparta;

isSparta = function (options, context) {  
    options = options || {};

    if (_.isEqual(options, 'sparta')) {
      return 'This is sparta!';
    }

    return 'No sparta today';
};

module.exports = isSparta;  

The next section shows the usage of the isSparta helper within a view. You pass options from a view to the helper by separating the helper call and its options with a space: {{isSparta 'sparta'}}. The isSparta helper returns a string which will be rendered in replace of your helper call within your view.

Helper With Multiple Options: function(option1, …, optionN, context)

You can pass multiple options from your view to the helper function. Hapi maps each option to the helpers signature which results in a separate variable for each option value.

Changing the isSparta helper from above to a helper with three options, changes the helper signature to: function(option1, option2, option3, context).

Helper Usage

Enough theory. Let’s look at code examples to get a better understanding which transformations can be made using handlebars helper functions.

The following snippet shows the usage of the isAdmin helper. The block definition starting with {{#isAdmin}} encapsulates additional HTML markup which is only rendered if the helper function returns with return context.fn(this).

<a href='/navigation/link1'>Navigation Link 1</a>  
<a href='/navigation/link2'>Navigation Link 2</a>

{{#isAdmin}}
    {{! show admin navigation}}
    <h2>Admin Navigation</h2>
    <a href='/super/secret/admin/link>Secret Link</a>
{{/isAdmin}}

Each block helper in handlebars ends with a slash followed by the helper name. In this case we need to end the encapsulating markup with {{/isAdmin}} to let handlebars know what will be rendered in case the helper returns successfully.

The example below illustrates the usage of options within handlebars helper functions. This time, we don’t define a block helper. Instead, we pass an additional option value to the helper. The helper returns a string value which replaces the helper definition within the view.

<h1>{{title}}</h1>  
<p>{{message}}</p>

{{! call test helper with option value}}
<p>{{isSparta 'sparta'}}</p>  
<p>{{isSparta 'option'}}</p>  

Using the isSparta helper from above, the rendered HTML of the view depends on the passed option value.

<h1>{{title}}</h1>  
<p>{{message}}</p>

<p>This is sparta!</p>  
<p>No sparta today</p>  

Both helper definitions within the view are replaced by the respective string returned from isSparta helper.

Please use the comments below or give us a shot @futurestud_io and let us know about any issues you’re having with handlebars helper functions in hapi.


Additional Resources

Explore the Library

Find interesting tutorials and solutions for your problems.