Introduction
Inside of our Node/Express applications, we will often want to render dynamic HTML pages based on data the we get from/send to our users. There are ways to do this without the use of a template engine, but templating engines make this SO much easier.
For our applications, we will use the Handlebars (hbs) engine to template our HTML.
Templating?
What does it mean to template something? Essentially we build out a blue print with placeholders that we can plug data into. This way, the structure of our HTML is static and in place. All we change is the content. And we can render this content dynamically based on the data itself or user interaction.
Example
Let's see this in action by making a project and getting our hands dirty:
- Navigate to your code directory:
cd ~/wcci/code
- Create a new project using the Express Generator:
express hello-handlebars --view=hbs
- Open the project in VSCode:
cd hello-handlebars && code .
- Install dependencies:
npm i
Now let's take a look at wha we've got.
Views
Inside of the views
directory, you should see 3 files that end in .hbs
. These files are hbs template files. Let's take a look at layout.hbs
:
<!DOCTYPE html>
<html>
<head>
<title>{{title}}</title>
<link rel="stylesheet" href="/stylesheets/style.css" />
</head>
<body>
{{{body}}}
</body>
</html>
layout.hbs
layout.hbs
is actually a special hbs file. It will be applied as a wrapper to any other hbs file that we load.
Hbs essentially looks at this as your standard page layout. The content of all other files will be applied inside of {{{body}}}
. So, for example, when we render index.hbs
, it's contents are passed to layout.hbs
and then rendered. So we end up with the following:
<!DOCTYPE html>
<html>
<head>
<title>{{title}}</title>
<link rel="stylesheet" href="/stylesheets/style.css" />
</head>
<body>
<h1>{{title}}</h1>
<p>Welcome to {{title}}</p>
</body>
</html>
{{title}} - what's that?
So we now have {{title}}
3 times in our page. This is one of the placeholders mentioned earlier. When we render a template through a controller, we can also pass an object containing dynamic data that will get rendered inside of our template before it is sent to our user in the browser. Open /routes/index.js
and let's have a look at how we actually render these templates.
/* GET home page. */
router.get("/", function(req, res, next) {
res.render("index", { title: "Express" });
});
So, when we hit the homepage, we're going to render this index.hbs
template. Note that we don't have to include the file extension in the render method. Express knows to look for an hbs template from the configuration we gave it when we bootstrapped the project. res.render
also accepts a second parameter, an object. The object we're passing has a title property. This corresponds to the {{title}}
element that we have in our template!
Run it
Let's see exactly what we get when we combine all of this. In Git Bash, run npm start
and navigate to localhost:3000
in your browser. Note that all of the instances of {{title}}
have been replaced by 'Express'!
Now we have three instances of 'Express' that we only had to write once. Try changing the content of title
in the object inside of res.render
and restart your application (or use Nodemon 🤗). You will see your new content rendered to the page!
These objects can be as large or small as you want. In this example, our object only has one property. But we can have as many as we want. Usually these objects will be created by querying a database for some piece of data and then passing the response from that query to res.render
. This is how we get REALLY dynamic pages.
Common Commands
Hbs can do a lot more for us than just render dynamic content. We can actually programmatically render views so that we don't have to know exactly what we'll get back, but we can still render our content as we intend regardless of the data we receive.
Rendering Collections
With hbs, we can take an array of any size and render it exactly as we intend using hbs' built in {{#each}}{{/each}}
method. Let's add the following to our index.js
route:
/* GET home page. */
router.get("/", function(req, res, next) {
res.render("index", {
title: "Express",
colors: ["red", "orange", "yellow"]
});
});
And this to index.hbs
:
<h1>{{title}}</h1>
<p>Welcome to {{title}}</p>
<ul>
{{#each colors}}
<li>{{this}}</li>
{{/each}}
</ul>
After a restart, your page should be rendering exactly as before but with a list of colors added! Play around with adding and removing colors from the list to see the true power hbs is giving you.
Conditional Rendering
We can also conditionally render elements on our page based on the presence or absence of a value. Let's add the following to index.hbs
:
<h1>{{title}}</h1>
<p>Welcome to {{title}}</p>
{{#if subtitle}} <small>{{subtitle}}</small> {{/if}}
<ul>
{{#each colors}}
<li>{{this}}</li>
{{/each}}
</ul>
Refresh your application and your browser. You shouldn't see anything different. But once you've done that, go into routes/index.js
and add a subtitle
property to the render object. Refresh all again and you should see that new property being rendered.
Conclusion
Templating engines are such useful tools when building dynamic webb applications. They allow us to make reuseable pieces of code that we can simply plug information into and render different pages to our user. And this is just the tip of the iceberg. Check out the Handlebars docs to see all of the amazing things you can do!