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 Handlebars. Handlebars.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 false
, undefined
, null
, ""
, 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! 🤓 :