Express - Dynamic Views 🤓

January 18, 2021

ExpressJS - the differences Static vs Dynamic page

The learning Goals of this article are as follows:

  • create views in Express
  • understanding the role of the dynamic templates and why we use them
  • understanding and use HandlebarJS for creating dynamic templates
  • use if, with, and each block helpers

One thing that can get confusing when trying to figure out what goes where is the whole Static and Dynamic pages and what exactly each of them mean and do. Static files are usually found in the /public directory which will contain the client side JavaScript, CSS files, and images. When you see those files you may think how are those static when they are technically doing something.

That is true but compared to other parts of the Models Views Controller (MVC) architecture they don’t have a lot of operations going on. To set this up we will have to make sure the code knows where to look when needing to retrieve code from the public directory. First you’ll want to make sure you have required Express with:

var express = require('express');

Once you have Express required along with any others that need to be required you can move on to your public directory like:

app.use(express.static(path.join(__dirname, 'public')));

Another part of the static portion is the Views of the MVC architecture which has some HTML on the server side. It again is considered static because there isn’t any data being processed in that file. It however does accept forms and allows users to input their data which then goes into a more dynamic file which is usually the models directory.

👉 When we signaled that we were going dynamic it meant there was going to be a lot of moving pieces and this is where things can go wrong quickly.

👉 The dynamic part allows us to have separate files to access different data and route it to where it needs to be.

const routes = require('./routes/index');
app.use('/', routes);

👉 This points us towards our routes directory which is a js file that now has code in it that is doing something similar to the app.js file. Which we have to require code in here as well:

const express = require('express');
const router = express.Router();
const Product = require('../models/product');

The last line which is directing us to the models directory. The models directory is for the most part where we are storing the data until we need to use any of it. The routes file has some functions in it which again makes it dynamic as it is getting data and directing it to another location. That piece of code looks like:

router.post('/cart', function(req, res, next) {
   let temp = parseInt(req.body.id);
   let product = Product.find(temp);
   req.session.cart.push(product);
   res.redirect('/');
});

To show that this is dynamic this code is parsing into another data structure and then finding that piece of data needed. It then is pushing that data into another file which is being posted possibly to a static file and then you are being redirected to the index file

Let's brake it even more simple:

👉 ExpressJS can send text to the browser with just a few lines of code:

const express = require('express');
const app = express();

app.get('/', (req, res, next) => {
  response.send('hello world');
});

app.listen(3000);

🤓 🔨👉 We can also send more complex HTML to the browser:

We refer to the arguments in our route’s callback as

request

and

response

as a demonstration.

These are represented commonly as req and res in the documentation, so we’ll use that going forward.

app.get('/hello', (req, res, next) => {
  res.send(`
    <!doctype html>
    <html>
      <head>
        <link rel="stylesheet" href="stylesheets/style.css">
      </head>
      <body>
        This is my second route
      </body>
    </html>
  `);
});

This way would be tedious and complicated as our application grows. Can you imagine our app.js having thousands of lines? There must be a better way!

In ExpressJS and most frameworks- you can create files specifically for our HTML. This way, we can keep the HTML separated from the logic of our application.

These files will be called views, and once we learn how to use them, we can simply call res.render instead of res.send and send an HTML file to the browser:

app.get('/', (req, res, next) => {
  res.render('index.html');
});

Dynamic Views

Views are templates for specifically HTML. HTML is what the client will see in their browser.

To start using views, we should create a folder inside of our project called views to group them. We will create our first view index.hbs:

$ mkdir views
$ touch views/index.hbs
$ tree .
.
├── app.js
├── package.json
├── stylesheets
│   └── style.css
└── views
    └── index.hbs

Notice we use a new extension

.hbs

instead of

.html

The advantage of separating views is that we separate the ExpressJS server logic (routes, server setup, server start, etc.) and the presentation (HTML), making our code more manageable and well structured.

ExpressJS won’t know by itself where we decided to group our views, but there is an easy fix. We can tell our Express app where to look for our views:

// creates an absolute path pointing to a folder called "views"
app.set('views', __dirname + '/views');

In Express, instead of using plain HTML, we can use a fancier version of HTML: hbs, or Handlebars.

We’ll get into more detail shortly about HBS, but for now, make sure you install it in our app:

$ npm install hbs

… And tell our Express app that HBS will be in charge of rendering the HTML:

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

Open the views/index.hbs file and add some content:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>My first view</title>
  <link rel="stylesheet" href="stylesheets/style.css">
</head>
<body>
  <h1>If you can dream it, you can code it!</h1>
  <img src="https://media.giphy.com/media/l0MYEqEzwMWFCg8rm/giphy.gif">
</body>
</html>

Finally, instead of res.send(). we have to tell Express to send and render our index view to the client:

app.get('/', (req, res, next) => {
  // send views/index.hbs for displaying in the browser
  res.render('index');
});

When we visit localhost:3000, we’ll see our HTML rendered!

Let's Practice

👉 Create a new route called about:

  • It should render a separate view also called about.hbs
  • Create an h1 with your name
  • Add a giphy image that you like

Handlebars

As we saw in the previous example, our file had a .hbs extension. This extension stands for HandlebarsHandlebars.js is a sweet javascript library for building clean logicless templates based on the Mustache Templating Language.

One of the essential features of using Handlebars is that we can make templates dynamic by sending information to them and using that data to render our web app.

The res.render() method can take an additional parameter that will contain a JavaScript object with information we can use in the view.

Let’s look at an example:

// app.js

app.get('/', (req, res, next) => {
  let data = {
    name: "Irene",
    techblog: "Irene WebDev"
  };

  res.render('index', data);
});
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Home</title>
</head>
<body>
  <h1>Hello {{name}}!</h1>
  <p>Welcome to my {{techblog}}!!</p>
</body>
</html>

Any key passed in the object will be available in the view, with a variable of the same name.

{{ variableName }} signifies that a variable will be output to the HTML we send to the client

Templates are mostly HTML, but HBS will analyze them and execute JavaScript before it renders the final HTML and sends it to the browser:

Handlebars - Scaping HTML

By default Handlebars escapes HTML values included in a expression with the {{ }}. That means if we send data like this:

app.get('/', (req, res, next) => {
  let data = {
    name: "Irene",
    techblog: "<span>Irene WebDev</span>"
  };
  res.render('index', data);
});

And then print it on our HBS file:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Home</title>
</head>
<body>
  <h1>Hello {{name}}!</h1>
  <p>Welcome to my {{techblog}}!!</p>
</body>
</html>

If we don’t want Handlebars to escape a value, we should use the triple-stash: {{{ }}}.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Home</title>
</head>
<body>
  <h1>Hello {{name}}!</h1>
  <p>Welcome to my {{{techblog}}}!!</p>
</body>
</html>

🛑 Built-In Helpers

Besides the dynamic template feature, Handlebars give us some great helpers to make our life easier when coding our web!

The if block helper

You can use the if helper to render a block conditionally. That means, if its argument returns falseundefinednull""0, or []Handlebars will not render the block.

app.get('/', (req, res, next) => {
  let data = {
    name: "Irene",
  };
  res.render('index', data);
});
<h1>Hello {{name}}!</h1>
{{#if lastName}}
    <h2>This won't be displayed!!</h2>
{{/if}}

Since lastName is undefined, our <h2> tag will not be displayed! Now, let’s add the lastName property to the data!

app.get('/', (req, res, next) => {
  let data = {
    name: "Irene",
    lastName: "Popova"
  };
  res.render('index', data);
});
<h1>Hello {{name}} {{lastName}}!</h1>
{{#if lastName}}
    <h2>This will be displayed!!</h2>
{{/if}}

We can also add an else statement, which makes this even more powerful!

<h1>Hello {{name}} {{lastName}}!</h1>
{{#if address}}
    <h2>This won't be displayed!!</h2>
{{else}}
  <h2>This will be displayed because the "address" property does not exists!!</h2>
{{/if}}

🛑 The unless block helper 🤔

You can use the unless helper as the inverse of the if helper. It will render the block if the expression returns a falsy value.

<h1>Hello {{name}} {{lastName}}!</h1>
{{#unless address}}
  <h3>WARNING: We cannot find this address!</h3>
{{/unless}}

If looking up address under the current context returns a falsy value, Handlebars will render the warning. Otherwise, it will render nothing. In our example, it will render the WARNING.

If we add the address property, then the warning should disappear!

app.get('/', (req, res, next) => {
  let data = {
    name: "Irene",
    lastName: "Popova",
    address: "Sherlock str"
  };
  res.render('index', data);
});

🛑 The each block helper

The each block helps us to iterate over a list of elements, mainly objects and array. Imagine printing a list of cities. We can do something like this:

<ul>
  <li>Berlin</li>
  <li>London</li>
  <li>Athena</li>
  <li>Munich</li>
  <li>Sofia</li>
</ul>

We are repeating the same <li> tag six times, only changing the content inside the tags. Using the each block, we can do the following:

First, we need to pass the data to our view:

app.get('/', (req, res, next) => {
  let data = {
    name: "Irene",
    lastName: "Popova",
    address: "Sherlock Str.",
    cities: ["Berlin", "London", "Athena", "Munich", "Sofia"]
  };
  res.render('index', data);
});

Once we have the data on our index.hbs file:

<ul>
  {{#each cities}}
    <li>{{this}}</li>
  {{/each}}
</ul>

Inside the block, you can use 👇

this

to reference the element we are iterating.

You can optionally provide an {{else}} section which will display only when the list is empty.

<ul>
  {{#each cities}}
    <li>{{this}}</li>
  {{else}}
    <p>No cities found yet!</p>
  {{/each}}
</ul>

🛑 @index

When looping through items in each, you can optionally reference the current loop index via {{@index}}

<ul>
  {{#each cities}}
    <li>{{@index}}: {{this}}</li>
  {{/each}}
</ul>

🛑 @key

Additionally for object iteration, {{@key}} references the current key name:

{{#each object}}
  {{@key}}: {{this}}
{{/each}}

🛑 @first - @last

The first and last steps of iteration are noted via the @first and @last variables when iterating over an array.

<ul>
  {{#each cities}}
    {{#if @first}}
      <li><b>{{this}}</b></li>
    {{else if @last}}
      <li><i>{{this}}</i></li>
    {{else}}
      <li>{{this}}</li>
    {{/if}}
  {{/each}}
</ul>

It is important to notice that the @first and @last helpers return a boolean! When iterating over an object, only the @first is available.

🛑 The with block helper

Commonly, Handlebars evaluates its templates against the context passed into the compiled method. We can shift that context to a section of a template by using the built-in with block helper.

For example, passing the following data:

app.get('/', (req, res, next) => {
  let data = {
    name: "Irene",
    lastName: "Popova",
    address: {
      street: "Sherlock str.",
      number: 66
    },
    cities: ["Berlin", "London", "Athena", "Munich", "Sofia"]
  };
  res.render('index', data);
});

We can do the following: 👇

<h1>Hello {{name}} {{lastName}}!</h1>
{{#with address}}
  <p>{{street}}, {{number}}</p>
{{/with}}

Using the with helper, we shift the context inside it, so we can refeer to {{address.street}} and {{address.number}}, as {{street}} and {{number}}.

Let's summarize

In this article, I gave you a brief introduction about templating with hbs.

⚠️ Ideally, you want to have as little logic as possible in your views, but using loops and the occasional if statement allows you to harness the real power of using a backend framework.

So dynamic means there is a lot happening in a short amount of time and if something is not functioning properly or defined properly other parts of the code will not function either. Whereas the static pages will still display something but it will not be showing the correct data if the dynamic pages are not operating as they should.

Extra Resources

Happy Coding! 🤓 :

Up next