Writing HTML sucks, thus we should do everything to minimize the time we spend writing it as much as possible.  Thus, we have Handlebars: a lightweight templating system for Node. Handlebars allows us to avoid repetitive code by compiling the final DOM structure of our site via logic, typically compiled by task runners such as Grunt or Gulp.

If you're involved in any sort of Node development, you're probably already familiar with Handlebars to a degree. I thought I was, but it isn't until we need to start a new project from scratch that we realize that we totally forgot the configuration process we took last time. That's why I'm here.

Let's have a quick refresher on the parts that make up Handlebars

  • Layouts are the most ambiguous high-level layer; these are commonly used to set underlying page metadata as well as general layout (for lack of a better term).
  • Pages are templates which equate to one type of page. For example, the 'post' page on this site is unique from, say, the homepage. Because all posts share elements with one another, hundreds of posts share this same template.
  • Partials are snippets which can be shared between pages, such as navigation.
  • A Context is content which is passed to templates and result in being the page's content
  • Helpers are the closest we get to logic in Handlebars: these allow us to display or omit content based on conditionals such as if statements. For example: showing an author's avatar only if they have uploaded an image.

Project Setup

We're going to use the Express /views folder to contain all of our handlebars goodness. Our project should look something like this:

myapp
├── bin
├── build
├── routes
├── src
├── views
│   ├── /layouts
│   ├── /partials
│   ├── error.hbs
│   ├── index.hbs
│   └── login.hbs
├── app.js
└── package.json
Example Express app

It's important to distinguish that we've separated our views folder into three classifications for layouts, partials, and pages, where pages occupy the root /views directory. It's important to keep this distinction as our structure affects how we serve up these templates.

Configure that Ish

Install handlebars:

$ npm install express-handlebars --save
Install Handlebars

Crack open your app.js file or whatever it is you call that thing. Require Handlebars:

const hbs = require('express-handlebars');
app.js

Next we'll configure Express to use Handlebars as the view engine, and tell Express where to find these files:

const hbs = require('express-handlebars');

// view engine setup
app.set('view engine', 'hbs');

app.engine( 'hbs', hbs( {
  extname: 'hbs',
  defaultView: 'default',
  layoutsDir: __dirname + '/views/pages/',
  partialsDir: __dirname + '/views/partials/'
}));
app.js

Express assumes by default that we're storing our views in the '/views' folder, which we are. We take this a step further by specifying which subfolders our partials and layouts are in above. We can save pages directly in /views.

Notice that we're also setting a default layout. We can override this in our routes if needed, but setting a default layout is useful for loading pages in an html wrapper container page metadata.

Kicks on Route 66

Let's create our first route in routes/index.js. We're going to load a view called home into a layout called default:

var express = require('express');
var router = express.Router();

router.get('/', function(req, res, next) {
  res.render('home', {layout: 'default', template: 'home-template'});
});

This will render views/home.hbs into views/layouts/default.hbs, provided are views are set up correctly. We also pass a custom value template which is user-defined; more on that below.

Basic Usage

Let's finally take a look at our actual Handlebars views. Here's default.hbs:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <title>Best Website</title>
  <meta name="HandheldFriendly" content="True" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
  <link rel="stylesheet" href="/css/main.min.css">
</head>
<body class="{{template}}">
  <div class="container">
	  {{{body}}}
  </div>
  {{> footer}}
</body>
</html>
default.hbs

We have three values here: {{template}} and {{{body}}}, and {{> footer}}.

{{template}} is a value with double brackets, thus is expecting linear data. We passed template in our route: this sets the body class to equal home-template on the chance that we'll want to apply page-specific styles or logic in the future.

{{{body}}} is rocking the triple brackets, and is reserved specifically to serve this purpose: loading templates into other templates.

Lastly we have {{> footer}}. This will load a partial named footer from views/partials/footer.hbs, provided that we create it. The difference between how {{{body}}} and {{> footer}} are being loaded have to do with a general workflow philosophy; pages are the main event and thus are loaded into layouts by their command. Partials can be called by pages at will whenever we please.

There's obviously a lot more to Handlebars- the fun doesn't truly begin until we pull dynamic values from databases or wherever. We'll get there.