hapi — How to Create a Dynamic Handlebars Layout Template

Hapi has great built-in support for various template engines like Handlebars or Jade, and HTML itself, of course.

However, the Hapi view documentation suffers from incomplete information when working with Handlebars and only presents rudimentary examples to integrate Handlebars and layouts. Composing a complex layout within a web project with partial views and dynamically inserted content from other files is straight forward when known how to do.

hapi Series Overview

Composing the Layout

We'll use the following code structure to separate server code, view configuration and the actual views from each other.

Code Structure

The following block presents the code structure we use for the examples. The views folder contains any .html files. Hapi renders the handlebars templates even though they don't have the .hbs file extension. The layout folder within views specifies the default template, used to dynamically include all other files into it.

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

The helpers and partials folder above can be used for Handlebars helpers and partial layouts. This guide doesn't make use of them and shows them to understand the view configuration options provided by hapi.

Server

The following code can be used to start a basic hapi server. The interesting part is the view configuration, done in lines 12 to 21: server.views({engines: …}).

var hapi = require('hapi');

// Create hapi server instance
var server = new hapi.Server();

// add connection parameters
server.connection({  
    host: 'localhost',
    port: 3000
});

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

// create your routes, currently it's just one
var routes = [  
    {
        method: 'GET',
        path: '/',
        handler: function(request, reply) {
            // Render the view with the custom greeting
            var data = {
                title: 'This is Index!',
                message: 'Hello, World. You crazy handlebars layout'
            };

            return reply.view('index', data);
        }
    }
];

// tell your server about the defined routes
server.route(routes);

// Start the server
server.start(function() {  
    // Log to the console the host and port info
    console.log('Server started at: ' + server.info.uri);
});

For all hapi view configuration options, have a look at the views documentation or Hapi views tutorial. In this example, we define the path to our views folder where hapi can find the .html templates. Additionally, we define a default layout with the layoutPath configuration and set the file name for our default layout for the layout option. Since the layout template is called default.html and located in views/layout/, hapi will reference the file with the defined values.

Hapi also ships with built-in support for handlebars helpers and partial views. It registers all helpers which are located as .js files in the path specified within helpersPath configuration. The same is true for partials.

Default Layout Template

The default layout template is used for every view. Hapi automatically uses this layout and includes the defined subview into it. The most important part within this file is the {{{content}}} block. This block is used by default to include the contents from every view into this layout file.

views/layout/default.html

    <!DOCTYPE html>
<html>  
<head>  
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <title>Your Title</title>
    <meta name="HandheldFriendly" content="True" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>  
<body>

    <div class="container">

        {{! Every view gets inserted here }}
        {{{content}}}

    </div>

</body>  
</html>  

Index View Template

The index view can be seen as a wildcard for any other view. Just define the view of your choice and add a route to it in the server configuration to show it.

The following snippet describes the layout of our index page. Remember: the contents of this file are included into the {{{content}}} block of default layout.

The first two lines in this file are comments and only add information to this file. It helps for situations when coming back to it after weeks or month of work on other things and you're diving back in.

The handlebars placeholder: {{title}} and {{message}}. These two placeholders are used to include the passed data from our handler. Recap the routes and look at the index route. We pass a data object to the view with corresponding keys: title and message.

views/index.html

{{!< layout/default}}
{{! The tag above means - insert everything in this file into the {content} of the default.html template }}

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

The index template will render like this with provided data:

<h1>This is Index!</h1>  
<p>Hello, World. You crazy handlebars layout</p>  

Result

The resulting HTML template of the index page will look like this:

    <!DOCTYPE html>
<html>  
<head>  
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <title>Your Title</title>
    <meta name="HandheldFriendly" content="True" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>  
<body>

    <div class="container">

        <h1>This is Index!</h1>
        <p>Hello, World. You crazy handlebars layout</p>

    </div>

</body>  
</html>  

In case you run into problems using our code snippets, please don't hesitate to contact us via twitter @futurestud_io.


Additional Resources

Explore the Library

Find interesting tutorials and solutions for your problems.