Express Views and Templates Using hbs

According to the article Using template engines with Express found in the Express documentation, “a template engine enables you to use static template files in your application. At runtime, the template engine replaces variables in a template file with actual values, and transforms the template into an HTML file sent to the client.”  Express supports many template engines. In the this chapter we will learn how to use part of the functionality provided by the handlebars.js (hbs) template/view engine.

Since we’ve designed our web app to have the same feel across pages (or endpoints), we realize that we have a lot of duplication in the html files.  As our pages are written now, if at some later time we want to make a functional or stylistic change to the head, navbar, or footer, then we would have to make the same changes in multiple files.

One solution to this duplication problem is to remove the common parts of our html files (e.g. the head, navbar, and footer) and place each part in a separate file.  At the same time, we replace each html file (which contains the duplicate code) with a template file that contains the code that is unique to that web page as well as references to the parts that we separated out.  The idea is depicted in the figure below.

When we create templates that reference the partials, if we need to make a change, to let’s say the footer, in all of the app’s pages, we only need to modify a single file, the footer partial file.

Since our template files contain only references to the partial pages, we need a way of joining all of the parts together so that the server can serve up a complete web page.  That’s where the template engine comes in.  The template engine reads a template file and builds an html file using the html code in the template file along with the html code in any partial that is referenced in the template.

The template engine will also allow us to dynamically replace variables in the template files with data that is computed using JavaScript before the page is rendered into html.  This is the true power of template engines.  We’ll see how to do that in the next chapter.

This project builds on the web app that you created in the previous chapter, so there is no need to start a new project.

Let’s begin.

Install the hbs module

In the VSC terminal, install the hbs module using npm.

$ npm install hbs

Update package.json

We’re going to be working with files that have the .hbs file extension, so lets tell nodemon to restart whenever we save a .hbs file.  To do so, just add hbs to the list of file types in the nodemonConfig property.

"nodemonConfig": {
    "ext": "js,html,css,hbs"
},

Create a Partials Directory

In the previous chapter we moved all of our html files from the static public directory into a directory named templates.  For this project we’re going to convert those html files into, wait for it …, you guessed it, templates.

We will also pull out all of the common code (e.g. head, nav-bar, footer, etc.) from the html files and place that code in separate partial files. We’ll place those partial files in a new directory named partials.

To start, create a new subdirectory named partials inside the templates directory. This is where we’ll store the partials.

app-dir/src/templates/partials

Rename your html Files

The hbs template engine requires the template files to have the .hbs file extension.  Rename each of the html files in the templates directory so that it has the .hbs file extension.  For example, index.html should be renamed to index.hbs.

Add Files to the Partials Directory

Before you begin modifying your website any further, understand that the instructions below assume that your head, nav-bars, and footers are identical across all of your web pages.  If this is not the case, for example if different pages have different content in the navbar, before you make any changes, please read this section and the next section titled Refactor Your Code.  You are free to create whatever partials are right for your web app.

In the newly created partials directory, create one new partial file for each of the partials that you want to create.  Remember a partial is a block of code that you want to move to a separate file, then essentially import into multiple template files.

An app that has 3 partials, one for the head, one for the navbar, and one for the footer would contain the following 3 partial files.

app-dir/src/templates/partials/head.hbs
app-dir/src/templates/partials/navbar.hbs
app-dir/src/templates/partials/footer.hbs

Note: If your web page has other blocks of code that is common in multiple web pages, feel free to create partials (in separate files) for them as well.

Refactor Your Code

The term refactoring refers to the process of improving your code by rewriting and/or reorganizing portions of code without adding new functionality.  This next step requires us to refactor the head, nav-bar, and footer code.

Start by opening one of your html files that contains a nav-bar and footer.  Then copy all of the nav-bar code, including the opening and closing nav tags, into navbar.hbs.  Next, copy all of the code that creates your footer, including the opening and closing div tags, (if you’re using divs), into footer.hbs.  Finally, copy the code that is between the opening and closing head tags, but don’t include the <head> and </head> tags themselves, and paste the code inside of head.hbs.  The reason why we don’t want to copy the opening and closing head tags, is because later we’ll want to add code to the head in some, but not all, of the template files.

Now we need to refactor all of the template files.  In particular, we need to replace all of the code in the template files that has been copied to the partials files with handlebars expressions.

A handlebars expression is an expression that begins with opening handlebars {{ and ends with closing handlebars }}.  To specify a partial, we will place, between the handlebars, a > symbol, followed by the name of the partial file (excluding the file extension).

For our app, in each of the template files we will:

    • remove the code between the opening and closing head tags (leaving the head tags in the template file) and replace the code between the head tags with {{>head}}
    • remove the navbar code and replace it with {{>navbar}}
    • remove the footer code and replace it with {{>footer}}

To illustrate this, your main.hbs template file might looks something like the following.

<!doctype html>
<html>
<head>
    {{>head}}
</head>
<body>
    {{>navbar}}

    <div class="container">
        <div class="row">
            <div class="col-6">

                <h3>Home of something cool!</h3>

            </div>
        </div>
    </div>

    {{>footer}}
</body>
</html>

Quite nice, right?

Some Things to Consider

    1. As described above, partials that are loaded from a directory are specified using their file names (without the file extension).  If the file name includes spaces or hyphens, the spaces and hyphens must be replaced with underscore characters.
    2. You are free to have as many partials as you’d like and you can put whatever code you wish in the partials.  Just remember that partials are used for duplicate code.  If you have duplicate code, consider putting that code in a partial.

Update Your Routers

Now for each of your routers, we no longer want to send an html file, rather we want to allow the handlebars view engine to render the pages.  To do this we call res.render() instead of res.sendFile().

Below is the contents of the login router in app-dir/src/routers/login.js.

const express = require('express')
const router = express.Router()

router.get('/login', (req, res) => {
    res.render('login')
})

module.exports = router

Update all of your routers so that they call res.render().

Update Your App’s Main

Below is the contents of app.js which includes the code necessary to set up and use the hbs template engine. Review the code and make any necessary changes to your app’s app.js file.

const express = require('express')
const path = require('path')
const hbs = require('hbs')

const indexRouter = require('./routers/index')
// import other routers here
const _404Router = require('./routers/404')

const app = express()

const dir = path.join(__dirname, "../public")
app.use(express.static(dir))

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

const viewsPath = path.join(__dirname, "./templates")
app.set('views', viewsPath)

const partialsPath = path.join(__dirname, "./templates/partials")
hbs.registerPartials(partialsPath)

app.use(indexRouter)
// register other routers here
app.use(_404Router)

const port = process.env.PORT || 3000

app.listen(port, () => {
    console.log('Server is up on port ' + port)
})

Explanation of the Code

Much of the code shown above you should be familiar with.  I included the contents of the entire file so that you can see where, in relation to the code you’ve already written, to add the new hbs code.  Let’s now look at the new additions to app.js.

One line 3 we load the hbs module into a constant named hbs.

Later, on line 14, we set express’ view engine (aka template engine) to 'hbs'.

On line 15 we construct a string, using path() that holds the absolute path to the templates directory.  Then on line 16, we tell express that it can find the views (aka templates) in the templates directory.

One line 19 we again use path() to construct a string, this time the string contains the path to the partials directory.  And on line 20 we tell hbs (not express) that we are using the partials found in the partials directory.

That’s all folks!

Test Your App

The changes that we made were not functional changes.  We simply refactored our code to utilize hbs templates and partials.

Test all of your endpoints to make sure they are working as they did before.

Upload to Heroku

When you’re satisfied that your app is working properly on localhost, push your code to Heroku. Test your app on the live site, and if anything is not working property; debug, push to Heroku, and repeat.

© 2022 – 2024, Eric McGregor. All rights reserved. Unauthorized use, distribution and/or duplication of this material without express and written permission from the author is strictly prohibited.