Express, Handlebars and Ghost Themes

In a previous post, I wrote about enhancing Vardyger's API by adding support for content negotiation (e.g., application/json or text/html). In this post, we'll enhance the API by adding support for the Handlebars templating engine and Ghost themes.


Handlebars is a popular extension of the Mustache templating language that is easy to integrate and provides a familiar syntax.

We'll start by installing the express-hbs package:

npm install express-hbs --save

Then we need to configure the API (in server.js) to use Handlebars as its default view engine:

var express = require('express'),
    hbs     = require('express-hbs');

var app = express();


// Vardyger/content/themes/{theme name}
var themeDir = path.resolve(__dirname + 
  '../../../content/themes/' + 'casper');

// Use `.hbs` as the default template extension
app.engine('hbs', hbs.express4({
  layoutsDir: themeDir,
  defaultLayout: themeDir + '/default.hbs',
  partialsDir: themeDir + '/partials'
app.set('view engine', 'hbs');
app.set('views', themeDir);


Note: Because we want to support Ghost themes we need to follow their file structure:

├── /assets
    └── /css
        ├── screen.css
    └── /fonts
    └── /images
    └── /js
├── /partials [optional]
├── default.hbs
├── index.hbs
├── page.hbs [optional]
├── post.hbs
├── package.json

Note: layouts (e.g., default.hbs) and views (e.g., post.hbs) are in the theme's root directory.

The Vardyger file structure:

├── /content
    └── /themes
        └── /casper
        └── /...
├── /core
    └── /client
    └── /server
        └── /api
        └── /...
    └── /shared

Ghost Themes

We've configured the API to use Handlebars but to render a post (GET /posts/{id}) using a Ghost theme we also need to configure the @blog property (in server.js) that provides access to global data properties in Ghost themes:

var themeData = {
  title: 'Rob Ferguson',
  description: 'My personal blog about technology ...',
  url: 'http://localhost:10010',
  navigation: true

hbs.updateTemplateOptions({ data: {blog: themeData} });

I also needed to create 'stubs' for most of Ghost's helpers:

And, to correctly format the (post) response object:

function formatResponse(model) {
  return {
    meta_title: model.metaTitle,
    meta_description: model.metaDescription,
    navigation: [{
      label: 'Home',
      url: url,
      current: false,
      slug: ''
    }, {
      label: 'About',
      url: url + '/about',
      current: false,
      slug: 'about'
    post: {
      id: model._id,
      title: model.title,
      excerpt: model.html,
      html: model.html,
      url: 'http://localhost:10010',
      image: model.image,
      featured: model.featured,
      published_at: model.publishedAt,
      updated_at: model.updatedAt,
      created_at: model.createdAt,
      author: {
        name: 'Rob Ferguson',
        location: 'Sydney, Australia'
      tags: ''

Now, run MongoDB:

ulimit -n 1024 && mongod --config /usr/local/etc/mongod.conf

Start the API server:

swagger project start

And try it out, from the command line:

curl http://localhost:10010/v1/posts/{id} --header "Accept: text/html"

Or, from your browser: