1. Code
  2. JavaScript

An Introduction to Handlebars

Scroll to top

If your site's data regularly changes, then you might want to take a look at Handlebars. Handlebars is a template processor that generates HTML pages dynamically. The ability to generate pages dynamically saves time by eliminating the need for manual updates. This tutorial will introduce Handlebars and teach you how to create a simple template for your website.


Site Template

There are two primary reasons why you'd want to make a template for your site. First, building a template encourages you to separate the logic-based code from the actual view, helping you adhere to the View/Controller pattern. Secondly, templates keep your code clean and maintainable, which, in turn, makes the process of updating your site a breeze.

It is important to note that you don't create a site with Handlebars. Instead, you create guidelines and structures that dictate how the site should look without focusing on a page's data.

Let's cover some of the basics.

The Basics

Handlebars generates HTML by executing a JSON structure through a template. These templates are written mostly in regular HTML and are peppered with placeholders that allow you to inject data as needed. For example, the following template greets the user when they log in:

1
<h1>Welcome back, {{name}}</h1>

The {{name}} attribute is where the user's name will be injected into the page. This placeholder corresponds with a property in the data's JSON structure. This is a basic example of how Handlebars works; however, you will soon see that everything else basically boils down to this simple concept. Let's move on to handling arrays.

Arrays

Handlebars has built-in helpers to assist you in working with more complex data. One of them is the each helper. This helper iterates through an array and allows you to create dynamic HTML per array element. For example, the following template displays an array of data that contains a list of the local concerts playing in my country:

1
<table>
2
    <tr>
3
		<th>Local Concerts</th>
4
	</tr>
5
	{{#each Concerts}}
6
		<tr>
7
			<td>{{this}}</td>
8
		</tr>
9
	{{/each}}
10
</table>
 

As you can see, this code is much cleaner than conventional code, such as using a loop in PHP or JavaScript to append HTML to a variable. Handlebars is not intrusive, and this is what makes it so accessible. You may also notice that we use the attribute name, this, to retrieve the current array element in the each loop.

This example is good for an array of simple values, but how do you handle more complex data? Well, you essentially do the same thing. For example, we're going to write a template for the following data:

1
[    
2
	{
3
		Name : "Band",
4
		Date : "Aug 14th, 2012",
5
		Albums : [
6
			{
7
				Name : "Generic Name"
8
			},
9
			{
10
				Name : "Something Else!!"
11
			}
12
		]
13
	},
14
	{
15
		Name : "Other Guys",
16
		Date : "Aug 22nd, 2012"
17
		Albums : [
18
			{
19
				Name : "Album One"
20
			}
21
		]
22
	}
23
]
 

We can easily display this information using the following template:

1
<table>
2
    <tr>
3
		<th>Band Name</th>
4
		<th>Date</th>
5
		<th>Album Name</th>
6
	</tr>
7
	{{#each Bands}}
8
		<tr>
9
			<td>{{Name}}</td>
10
			<td>{{Date}}</td>
11
			<td>{{Albums.0.Name}}</td>
12
		</tr>
13
	{{/each}}
14
</table>

In Handlebars, you can even access nested properties, like in the example above (Albums.0.Name), which retrieves the name of the first index of albums, and of course, you could have used another each loop to iterate over a band's albums.

It's worth noting that besides the dot notation to access nested properties, you can also use "../" to access a parent's properties.

What if there aren't any bands playing? You certainly don't want an empty table, and Handlebars thankfully provides if, else, and unless helpers. The if and else statements work like most programming languages: if the object you pass is false, then the else statement executes. Otherwise, the if statement executes. The unless statement is pretty interesting; it's essentially an inverted if statement. If the expression is true, the unless block will NOT run. So let's incorporate these helpers into our code:

1
{{#if Bands}}
2
    <table>
3
		<tr>
4
			<th>Band Name</th>
5
			<th>Date</th>
6
			<th>Album Name</th>
7
		</tr>
8
		{{#each Bands}}
9
			<tr>
10
				<td>{{Name}}</td>
11
				<td>{{Date}}</td>
12
				<td>{{Albums.0.Name}}</td>
13
			</tr>
14
		{{/each}}
15
	</table>
16
{{else}}
17
	<h3>There are no concerts coming up.</h3>
18
{{/if}}
 

Custom Helpers

Handlebars gives you the ability to create your own custom helper. You would simply register your function into Handlebars, and any template you compile afterward can access your helper. There are two kinds of helpers that you can make:

  • Function helpers are basically regular functions that, once registered, can be called anywhere in your template. The handlebars write the function's return value into the template.
  • Block helpers are similar in nature to the if, each, etc. helpers. They allow you to change the context of what's inside.

Let us see a quick example of each helper. First, we will register a function helper with the following code:

1
Handlebars.registerHelper("Max", function(A, B){
2
    return (A > B) ? A : B;
3
});
 

The first argument passed to registerHelper() is the name of the customer helper; this name will be used in the template. The second argument is the function associated with this helper.

Using this helper in a template can be seen as:

1
{{Max 12 45}}
 

This template uses the Max helper, and passes the values 12 and 45 to the associated function. Handlebars function helpers support multiple parameters. You can directly insert numbers into the template itself, or you can use attributes from a JSON structure.

Now let's look at a custom block helper. Block helpers allow you to set the context before running the code contained within the block. For example, consider the following object:

1
{
2
    Name: "Parent",
3
	Sub: {
4
		Name: "Child"
5
	}
6
}
 

In order to display both names, you can write a block helper that runs the template once with the parent's context and once with the child's context. Here is the helper:

1
Handlebars.registerHelper("BothNames", function(context, options){
2
    return options.fn(context) + options.fn(context.Sub);
3
});
 

And the template looks like this:

1
{{#BothNames this}}
2
    <h2>{{Name}}</h2>
3
{{/BothName}}
 

The hashtag preceding the helper's name informs Handlebars that this is a block helper, and you close the block similarly to how you would close an HTML tag. The options.fn function runs the section of the template inside the block with whatever context you give it.

Now that we have the basics down, let's start creating a full demo.


Building a Site Template

The template we will build is for a recipe site. This will give you a good understanding of Handlebars, as it encompasses getting data from an API and passing it through a template.

Setting Up a Handlebars Project

We must first load our template script, but in order to do that, we need to create a new HTML file and include our Handlebars library:

1
<html>
2
    <head>
3
		<title>Handlebars Demo</title>
4
	</head>
5
	<body>
6
		<script id="myTemplate" type="text/x-handlebars-template">
7
		</script>
8
         <!-- Include Axios and Handlebars libraries -->
9
        <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
10
        <script src="https://cdn.jsdelivr.net/npm/handlebars/dist/handlebars.min.js"></script>
11
    
12
        <script src="handlebar.handlebars"></script>
13
	</body>
14
</html>
 

For convenience, you can store your template in a <script /> element and load it with JavaScript. This is much cleaner than storing it directly in a JavaScript variable.

Now let's discuss how this app is going to work. First, the app connects to an external API to pull in information on some recipes. We will be using TheMealDB as an external API. Next, we pass this info to Handlebars and run it through the template. Finally, we would create a handlebar.handlebars file that would contain the logic for handling templating with Handlebars.

1
function renderData(data) {
2
  const source = document.getElementById("myTemplate").innerHTML;
3
  const template = Handlebars.compile(source);
4
  const renderedHTML = template({ items: data.meals });
5
  document.getElementById("content").innerHTML = renderedHTML;
6
}
7
8
// Main function to fetch data and render it

9
function main() {
10
  fetchData().then((data) => renderData(data));
11
}
12
13
// Call the main function when the DOM is ready

14
document.addEventListener("DOMContentLoaded", main);

This is the complete code for compiling and generating HTML code from a template. You can technically pass the JSON data from the API directly into Handlebars just above the renderData() function, but we would be passing the JSON data into a separate file: recipe.js. So before we start building the template, let's take a look at the file.

Getting the Data

The MealDB API is simple to consume as it has a free version and it does not involve any form of authentication. The URL can be used within an Axios request, as shown in the script below.

1
function fetchData() {
2
  const url = "https://www.themealdb.com/api/json/v1/1/search.php?s=cake";
3
  return axios
4
    .get(url)
5
    .then((response) => response.data)
6
    .catch((error) => {
7
      console.error("Error fetching data:", error);
8
      return [];
9
    });
10
}
 

By building your site with a Handlebars template, you can produce a full site's worth of code in only a few lines. Here is the entire template:

1
<!DOCTYPE html>
2
<html>
3
  <head>
4
    <title>Handlebars Demo</title>
5
    <link rel="stylesheet" href="styles.css" />
6
  </head>
7
8
  <body>
9
    <div id="content" class="item"></div>
10
11
    <script id="myTemplate" type="text/x-handlebars-template">
12
      <h1>&Xi;RecipeCards
13
        <span id="BOS">Recipe search powered by
14
          <a id="Logo" href="https://www.themealdb.com/">
15
            <img src="https://www.themealdb.com/images/logo-small.png" />
16
          </a>

17
        </span>

18
      </h1>

19
      <div class="flex-container">
20
21
        {{#each items}}
22
          <div class="box">
23
            <img src="{{this.strMealThumb}}" alt="{{this.strCategory}}" />
24
            <h2>{{this.strMeal}}</h2>

25
            <ul>
26
              <h4>Instructions:</h4>

27
              <li> {{this.strInstructions}}</li>

28
            </ul>

29
          </div>

30
        {{/each}}
31
      </div>

32
    </script>
33
34
    <!-- Include Axios and Handlebars libraries -->
35
    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
36
    <script src="https://cdn.jsdelivr.net/npm/handlebars/dist/handlebars.min.js"></script>
37
38
    <script src="handlebar.handlebars"></script>
39
  </body>
40
</html>
 

Let's run through this code. The first few lines are just the logo at the top of the page. Then for each recipe, we create a recipe 'card' with a picture, name, and instructions.

The API returns a list of tags for each item. We can write a function helper called getTags that takes this strTag information and returns all the tags for each dish. In order for this template to work, we need to load in the getTag helper into Handlebars before parsing the template. So within the handlebars.js at the beginning of the script section, add the following code:

1
Handlebars.registerHelper("getTags", function (recipes) {
2
    
3
  let tags = [];
4
  
5
  recipes.forEach((recipe) => {
6
      
7
    tags.push(recipe.strTags);
8
    
9
  });
10
11
  return `Tags : ${tags}`;
12
});
 

Now, whenever Handlebars sees getTags, it calls the associated function and retrieves the tag information.

At this point, you are free to play around and design the template however you wish, but you will most likely see that this process is slow. This is primarily due to the three API calls before Handlebars loads the page. Obviously, this is not ideal, but precompiling your template can help.

recipe-siterecipe-siterecipe-site
 

Precompiling

You have two different options when it comes to Handlebars. The first is to just precompile the actual template. This reduces the loading time, and you won't have to include the Handlebars compiler with your page. This also results in a smaller file size, but this doesn't really help in our scenario.

Our problem is the communication between the browser and the API. If you did want to precompile your template, you can download the Node.js package through npm with the following command:

1
npm install handlebars -g
 

You may need to do this as root (i.e. add 'sudo' before the command). Once installed, you can create a file for your template and compile it like so:

1
handlebars demo.handlebars -f demo.js
 

You should give your template file a .handlebars extension. This is not mandatory, but if you name it something like demo.html, then the template's name will be "demo.html" as opposed to just "demo". After naming your template, simply include the output file along with the run-time version of Handlebars (you can use the regular version, but it's larger) and type the following:

1
var template = Handlebars.templates['demo'];
2
var html = template({ Your Json Data Here });

But, as I mentioned before, this doesn't really help us in this scenario. What then can we do? Well, we can precompile and output the entire file. This makes it so that we can run the template with data and save the final HTML output—caching, in other words. This drastically speeds up the load time of your application. Unfortunately, client-side JavaScript doesn't have file IO capabilities. So the easiest way to accomplish this is to just output the HTML to a text box and manually save it. Be aware of an API's guidelines on caching. Most APIs have a maximum amount of time that data can be cached for; make sure to find that information before saving static pages.


Conclusion

This has been a quick introduction to Handlebars. Moving forward, you can look into "Partials": small templates that can be used like functions. 

Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.