How to Use Jade and Handlebars in Express.js

How to Use Jade and Handlebars in Express.js

I hated Jade as many other Node.js developes do.  But I changed 180 after I realized that it has tons of features.

At Storify and DocuSign we used Jade for EVERYTHING. We used Jade even in the browser. There is a little trick called jade-browser. It was developed by folks at Storify. I maintained it for a bit.

The funny thing is that DocuSign team used jade-browser long before they met me. They swear they hired me without knowing that I was involved in that library. :-)

Anyway, after covering Jade and Handlebars in previous posts, it’s time to apply them to do some real work. In this post, I’ll cover:

  • Jade and Handlebars usage in Express.js 4
  • Project: adding Jade templates to Blog

By default, Express.js 4.x (and 3.x) uses either a template extension provided to the res.render method or the default extension set by the view engine setting, to invoke the require and __express methods on the template library. In other words, for Express.js to utilize a template engine library out of the box, that library needs to have the __express method.

When the template engine library doesn’t provide the __express method, or a similar one with (path, options, callback)parameters, it’s recommended that you use Consolidate.js (https://github.com/visionmedia/consolidate.js/).

Here is a quick example of Consolidate.js for Express.js 4 (version 4.2.0 and Consolidate version is 0.10.0):

var express = require('express'),
  cons = require('consolidate'),
  app = express()

app.engine('html', cons.swig)

app.set('view engine', 'html')
app.set('views', __dirname + '/views')

var platforms = [
  { name: 'node' },
  { name: 'ruby' },
  { name: 'python' }
]

app.get('/', function(req, res){
  res.render('index', {
    title: 'Consolidate This'
  })
})
app.get('/platforms', function(req, res){
  res.render('platforms', {
    title: 'Platforms',
    platforms: platforms
  })
})

app.listen(3000)
console.log('Express server listening on port 3000')

Usually, the source code is in the GitHub repository and the snippet is in the
ch4/consolidate folder.

For more information on how to configure Express.js settings and use Consolidate.js, refer to the Pro Express.js 4 book (Apress, 2014).

Jade and Express.js

Jade is compatible with Express.js out of the box (in fact, it’s the default choice), so to use Jade with Express.js, you just need to install a template engine module jade (https://www.npmjs.org/package/jade) and provide an extension to Express.js via the view engine setting).

For example, in the main server file we set the setting:

app.set('view engine', 'jade');

■ Note If you use $ express <app_name> command-line tool, you can add the option for engine support, i.e.,–e option for EJS and –H for Hogan. This will add EJS or Hogan automatically to your new project. Without either of these options, the express-generator (versions 4.0.0–4.2.0) will use Jade.

In the route file, we can call the template—for example, views/page.jade (the views folder name is another Express.js default, which can be overwritten with the
view setting):

app.get('/page', function(req, res, next){
  //get the data dynamically
  res.render('page', data);
});

If we don’t specify the views engine setting, then the extension must be passed explicitly to res.render():

res.render('page.jade', data);

Handlebars and Express.js

Contrary to Jade, the Handlebars library from http://handlebarsjs.com/ doesn’t come with the __express method, but there are a few options to make Handlebars work with Express.js:

  • consolidate: a Swiss-army knife of Express.js template engine libraries (shown above)
  • hbs(https://github.com/donpark/hbs): wrapper library for Handlebars
  • express-Handlebars(file://pchns-f01/TECHNOLOGY/BPR/Techutilities/Apress/Apress%20Outline/express3-handlebars): despite the name, this module should work just fine with Express.js 4 as well as version 3.x

Here’s how we can use hbs approach (extension hbs). Inside of the typical Express.js app code (i.e., configuration section of the main file that we launch with the $ node command) write the following statements:

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

Or, if another extension is preferable, such as html, we see the following:

...
app.set('view engine', 'html');
pp.engine('html', require('hbs').__express); 
...

The express3-handlebars approach usage is as follows:

...
app.engine('handlebars', exphbs({defaultLayout: 'main'}));
app.set('view engine', 'handlebars');
...

Project: Adding Jade Templates to Blog

Last, we can continue with Blog. In this section we add main pages using Jade, plus add a layout and some partials:

  • layout.jade: global app-wide template
  • index.jade: home page with the list of posts
  • article.jade: individual article page
  • login.jade: page with a login form
  • post.jade: page for adding a new article
  • admin.jade: page to administer articles after logging in

The demo where we’ll plug in the MongoDB database is in the ch5 folder of the GitHub repository practicalnode: https://github.com/azat-co/practicalnode. So the source code for the Jade templates is exactly the same as in that GitHub project. Feel free to copy it from there or follow the instructions below.

layout.jade

Let’s open the project where we left off in the ch3 from https://github.com/azat-co/practicalnode and add layout.jade with the document type statement:

doctype html

■ Note doctype 5 was deprecated around v1.0. Now we can add the main tags of the page:

html
  head

The title of the each page is provided from the appTitle variable (aka, local):

title= appTitle

Then, in the head tag, we list all the front-end assets that we need app-wide (on each page):

script(type="text/javascript", src="js/jquery-2.0.3.min.js")
link(rel='stylesheet', href='https://azat.wpenginepowered.com/css/bootstrap-3.0.2/css/bootstrap.min.css')
link(rel="stylesheet", href="https://azat.wpenginepowered.com/css/bootstrap-3.0.2/css/bootstrap-theme.min.css")
link(rel="stylesheet", href="https://azat.wpenginepowered.com/css/style.css")
script(type="text/javascript", src="https://azat.wpenginepowered.com/css/bootstrap-3.0.2/js/bootstrap.min.js")
script(type="text/javascript", src="https://azat.wpenginepowered.com/js/blog.js")
meta(name="viewport", content="width=device-width, initial-scale=1.0")

The main content lives in body which has the same level indentation as head:

body

Inside the body, we write an ID and some classes for the styles that we’ll add later:

#wrap
  .container

The appTitle value is printed dynamically, but the p.lead element only has texts:

h1.page-header= appTitle
p.lead Welcome to example from Express.js Experience by&nbsp;
a(href="http://twitter.com/azat_co") @azat_co
|. Please enjoy.

The block sections can be overwritten by the children templates (templates that extend this file):

block page
block header
  div

Menu is a partial (i.e., an include) that is stored in the views/includes folder. Note the absence of quotation marks:

include includes/menu

In this block, we can display messages for users:

block alert
  div.alert.alert-warning.hidden

Main content goes in this block:

.content
  block content

Lastly, the footer looks as follows:

block footer
  footer
    .container
      p
        | Copyright &copy; 2014 | Issues? Submit to a(href="https://github.com/azat-co/blog-express/issues") GitHub
        | .

The full code of layout.jade is as follows:

doctype html
html
  head
    title= appTitle
    script(type="text/javascript", src="js/jquery-2.0.3.min.js")
    link(rel="stylesheet", href="https://azat.wpenginepowered.com/css/bootstrap-3.0.2/css/bootstrap.min.css")
    link(rel="stylesheet", href="https://azat.wpenginepowered.com/css/bootstrap-3.0.2/css/bootstrap-theme.min.css")
    link(rel="stylesheet", href="https://azat.wpenginepowered.com/css/style.css")
    script(type="text/javascript", src="https://azat.wpenginepowered.com/css/bootstrap-3.0.2/js/bootstrap.min.js")
    script(type="text/javascript", src="https://azat.wpenginepowered.com/js/blog.js")
    meta(name="viewport", content="width=device-width, initial-scale=1.0")
  body
    #wrap
      .container
        h1.page-header= appTitle
        p.lead Welcome to example from Express.js Experience by&nbsp;
          a(href="http://twitter.com/azat_co") @azat_co
          |. Please enjoy.
        block page
        block header
          div
            include includes/menu
        block alert
          div.alert.alert-warning.hidden
        .content
          block content
    block footer
      footer
        .container
          p
            | Copyright &copy; 2014 | Issues? Submit to
            a(href=" https://github.com/azat-co/blog-express/issues") GitHub
            | .

index.jade

Now we can look at the home page template index.jade that extends layout:

extends layout

We set the menu variable to index, so the menu include (i.e., menu.jade) can determine which tab to show as active:

block page
  - var menu = 'index'

The main content with the list of articles that comes from locals is as follows:

block content
  if (articles.length === 0)
    | There's no published content yet.
    a(href="/login") Log in
    | to post and publish.
  else
    each article, index in articles
      div
        h2
          a(href="/articles/#{article.slug}")= article.title

The full code of index.jade is as follows:

extends layout

block page
  - var menu = 'index'
block content
  if (articles.length === 0)
    | There's no published content yet.
    a(href="/login") Log in
    | to post and publish.
  else
    each article, index in articles
      div
        h2
          a(href="/articles/#{article.slug}")= article.title

Figure 4–4 shows how the home page looks after adding style sheets.

Figure 4–4. The home page
Figure 4–4. The home page

article.jade

The individual article page (Figure 4–5 ) is relatively unsophisticated because most of the elements are abstracted into layout.jade:

extends layout

block content
  p
    h1= title
    p= text 
Figure 4–5. The article page
Figure 4–5. The article page

login.jade

Similarly, the login page contains only a form and a button (with the Twitter Bootstrap classes/markup):

extends layout

block page
  - var menu = 'login'

block content
  .col-md-4.col-md-offset-4
    h2 Log in
    div= error
    div
      form(action="/login", method="POST")
        p
          input.form-control(name="email", type="text", placeholder="hi@azat.co")
        p
          input.form-control(name="password", type="password", placeholder="***")
        p
          button.btn.btn-lg.btn-primary.btn-block(type="submit") Log in
        p
          input.form-control(name="password", type="password", placeholder="***")
        p
          button.btn.btn-lg.btn-primary.btn-block(type="submit") Log in

Figure 4–6 shows how the login page looks.

Figure 4–6. The login page
Figure 4–6. The login page

post.jade

The post page (Figure 4–7 ) has another form. This time, the form contains a text area element:

extends layout
block page
  - var menu = 'post'
block content 
h2 Post an Article
div= error
div.col-md-8
  form(action="/post", method="POST", role="form")
    div.form-group
      label(for="title") Title
      input#title.form-control(name="title", type="text", placeholder="JavaScript is good")
    div.form-group
      label(for="slug") Slug
      input#slug.form-control(name="slug", type="text", placeholder="js-good")
      span.help-block This string will be used in the URL.
    div.form-group
      label(for="text") Text
      textarea#text.form-control(rows="5", name="text", placeholder="Text")
    p
      button.btn.btn-primary(type="submit") Save 
Figure 4–7. The post page
Figure 4–7. The post page

admin.jade

The admin page (Figure 4–8 ) has a loop of articles just like the home page. In addition, we can include a front-end script (js/admin.js) specific to this page:

extends layout

block page
  - var menu = 'admin'

block content
  div.admin
    if (articles.length === 0 )
      p
        | Nothing to display. Add a new
        a(href="/post") article
        |.
    else

      table.table.table-stripped
        thead
          tr
            th(colspan="2") Actions
            th Post Title
        tbody
          each article, index in articles
            tr(data-id="#{article._id}", class=(!article.published)?'unpublished':'')
              td.action
                button.btn.btn-danger.btn-sm.remove(type="button")
                  span.glyphicon.glyphicon-remove(title="Remove")
              td.action
                button.btn.btn-default.btn-sm.publish(type="button")
                  span.glyphicon(class=(article.published)?"glyphicon-pause":"glyphicon-play",
title=(article.published)?"Unpublish":"Publish")
              td= article.title
      script(type="text/javascript", src="js/admin.js") 
Figure 4–8. The admin page
Figure 4–8. The admin page

We use interpolation to print article IDs as attributes data-id:

tr(data-id="#{article._id}", class=(!article.published)?'unpublished':'')

And, a conditional (ternary) operator (https://github.com/donpark/hbs) is used for classes and title attributes. Remember, it’s JavaScript!

                  span.glyphicon(class=(article.published)?"glyphicon-pause":"glyphicon-play",
title=(article.published)?"Unpublish":"Publish") 

Summary

You learned about Jade and Handlebars templates (variables, iterations, condition, partials, unescaping, and so forth), and how to use them in a standalone Node.js script or within Express.js. In addition, the main pages for Blog were created using Jade.

In another tutorial, we examined an important aspect of modern web development and software engineering: test-driven development. We looked at the Mocha module and wrote some tests for Blog in true TDD/BDD style.

The example with a database added to Blog to populate these templates is in ch5 of https://github.com/azat-co/practicalnode. It shows you how to turn Jade templates into working HTML pages!

Author: Azat

Techies, entrepreneur, 20+ years in tech/IT/software/web development expert: NodeJS, JavaScript, MongoDB, Ruby on Rails, PHP, SQL, HTML, CSS. 500 Startups (batch Fall 2011) alumnus. http://azat.co http://github.com/azat-co

3 thoughts on “How to Use Jade and Handlebars in Express.js”

  1. Cool how you advertise this as a Handlebars and Jade tutorial then just use Jade

  2. Your website is restricting me from seeing an entire column portion of your article. (hopefully that makes sense)

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.