EJS: Embedded JavaScript Templating

One of the fundamental things that web frameworks do for us is to make it easier to combine dynamic data (which the app has looked up or computed) with a static HTML template to create a web page. The web page is therefore dynamically computed by the app.

There are several templating systems in common use with Express.

One very common and popular templating system for Express is Jade; indeed, it's the default. I have decided against it for CS304 because it's essentially a whole new language, substituting for HTML. The one I've chosen is EJS — Embedded JavaScript Templating. With EJS, the template looks like HTML, but you can insert special tag-like things that allow you to use JavaScript to create HTML elements. The result is a little ugly, but relatively easy to learn, because it just combines two languages we know: HTML and JS.

This reading introduces EJS for this semester.

EJS Tutorials

Because EJS is a well-known templating engine, there are some good tutorials on the web. Feel free to consult one of these for a different presentation.

But you can get the essentials of EJS from this reading.

First Example

Let's start with a relatively simple example, based on our Hello World example. Instead of replying with a fixed string, "Hello World!", we'll render a template for a complete web page, and the dynamic data will be the current time and the number of times the page has been visited (just a global counter).

Here's the relevant part of the server.js file:

var visitCounter = 0;

// This is our simple Hello World example, this time, rendering a template
app.get('/', (req, res) => {
    let now = new Date();
    visitCounter ++;
    return res.render('index.ejs', 
                      {visits: visitCounter, 
                       time: now.toLocaleTimeString()});
});

The last statement renders the template, where "rendering" means to combine a template (the first argument) with the dynamic data (the second argument) and then send the result to the browser.

The template file looks like this:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <title>Node Starter</title>
    <!-- load local stylesheet (css) -->
    <link rel="stylesheet" href="/styles.css" />
    <!-- loading jquery -->
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
</head>
<body>
    <h1>Welcome!</h1>

    <p>Everything is fine.</p>

    <p>This visit is at <%= time %>.</p>

    <p>There have been <%= visits %> visits since the app started.</p>

</body>
</html>

Almost all of that is HTML that you are familiar with. But the two differences are these lines:

    <p>This visit is at <%= time %>.</p>

    <p>There have been <%= visits %> visits since the app started.</p>

The tag-like syntax <%= expression %> is how we substitute some dynamic data into the template. The first inserts the time value from the data; the second inserts the visits.

Look back at the render function call:

    return res.render('index.ejs', 
                      {visits: visitCounter, 
                       time: now.toLocaleTimeString()});

The second argument is a dictionary. The keys, visits and time correspond to expressions in the template file. The values are what replaces the keys.

Setup

A few things have to be set up first. We have to configure our Express app to use EJS, so the following line gets added to the prelude part of the server:

app.set("view engine", "ejs");

A "view engine" is a templating engine, because these dynamically rendered pages are called views. (Recall the MVC: Model-View-Controller way of thinking about a web application.)

Secondly, the index.ejs file is stored in a folder called views in the same folder as the server file. All our templates will be stored in the views folder. Our app structure looks like this:

app/
    connection.js -> ../connection.js
    cs304.js -> ../cs304.js
    node_modules -> ../node_modules
    server.js
    views/
        index.ejs

That's all the setup that is necessary for EJS.

Tags

Just being able to substitute one expression is a nice start, but templating engines give us a lot more power. Here is a partial list of the tags in EJS:

  • <%= Outputs the value into the template (HTML escaped)
  • <%- Outputs the unescaped value into the template
  • <% 'Scriptlet' tag, for control-flow, no output.
  • <%# Comment tag, no execution, no output
  • <%% Outputs a literal <%
  • %> Plain ending tag

There are other features which we'll get into if needed.

The first item on that list is the one we've seen in our example: it substitutes a value into the template.

The second does the same thing, but without "escaping". Substituting "unescaped" data involves a small security risk involving XSS attacks, which we will discuss later in the course. So for now, stick to the first tag.

The third is an important tag, because it allows us to have control flow: conditionals and loops. We'll discuss that in the next few sections.

All the other tags are straightforward: we can insert comments into our templates that then get removed when the template is rendered (unlike, say, HTML comments, which would get sent to the browser). So, if we're feeling uncharitable:

<%# this section is for users who are idiots %>
<p>You can read more about <a href="FAQ.html"> <%= topic %> </a>.</p>

The two most common things we want to do with templates, other than substituting a single expression, are conditionals and loops. Let's cover those next.

Conditionals

Suppose that our app has a way to log someone in and the user id is stored in a variable. In the template for the home page, we want to put a login form, but only if the person is not logged in. If they are logged in, we want to put a customized logout button.

Here's the server code:

app.get('/home', (req, res) => {
    let userId = getUserId();
    return res.render('home.ejs', {userId: userId});
});

It's pretty simple; it just gets the user id and passes it to the render function.

The HTML will look like this, with the lines of a JS if statement spliced into the HTML using the <% %> syntax:

    <% if( userId === null ) { %>

    <% } else { %>

    <% } %>

We can then put HTML, including other EJS stuff, in the two branches, so the finished template looks like this:

    <% if( userId === null ) { %>

        <%# login form, because user not logged in %>
        <form action="/login" method="post">
            <label>username <input type="text" name="username"></label>
            <label>password <input type="password" name="password"></label>
            <input type="submit" value="login">
        </form>

    <% } else { %>

        <%# logout button %>
        <form action="/logout" method="post">
            <button>logout <%= userId %></button>
        </form>

    <% } %>

Notice the comments, which will be omitted from the rendered page.

Now let's look at loops.

Loops

So far, I've asked you to avoid loops in JavaScript because I wanted you to practice with callback function arguments using forEach. And we can use forEach in EJS as well.

The syntax is not too bad, though. Recall that, to use forEach() over an array, we just do:

array_variable.forEach( function (elt) {

    // body of "loop"

});

So, we need to splice the beginning and end of the forEach into our template:

<% array.forEach( function (elt) { %>

   <!-- loop body here -->

<% }) %>

Here's an example, where we might have a list of names and we want to create a bullet list of those names:

    <h1>List of <%= listDescription %></h1>

    <ul>
        <% list.forEach( (elt) => {%>

        <li><%= elt %></li>

        <% }) %>
    </ul>

In the code above, I used an arrow function, but either kind of function is fine.

The server code might be as simple as:

const ourHeroes = ['Harry', 'Ron', 'Hermione', 'Fred', 'George'];

app.get('/heroes', (req, res) => {
    return res.render('list.ejs', 
                       {listDescription: 'Some Heroes of the Harry Potter Saga',
                        list: ourHeroes});
});

Compound Data

So far, we've just been passing in strings and other "atomic" data. What about passing in dictionaries or lists of dictionaries? This isn't any harder, because we have the power of JavaScript with EJS. So, supposing we have an array of dictionaries as our data:

const ourHeroes2 = [{nm: 1, name: 'Harry'},
                    {nm: 2, name: 'Ron'},
                    {nm: 3, name: 'Hermione'},
                    {nm: 6, name: 'Fred'}, 
                    {nm: 7, name: 'George'}];

app.get('/heroes2', (req, res) => {
    return res.render('list.ejs', {listDescription: 'Our Heroes',
                                   list: ourHeroes});
});

The EJS is barely different. We just use elt.name to access the name property:

    <h1>List of <%= listDescription %></h1>

    <ul>
        <% list.forEach( (elt) => {%>

        <li><%= elt.name %></li>

        <% }) %>
    </ul>

Partials

EJS has one more cool trick. Very often web pages of a multi-page site have common features: a navbar, a logo, a header or footer, login forms, and so forth. We could copy/paste these from one template to another, but copy/paste is difficult to maintain: if you make a change to the navbar, you then have to go and update every place that you copied it to.

Copy/paste violates the DRY principle: Don't Repeat Yourself. While copy/paste has its place, it's useful to be able to avoid it when we want to, as with repeated chunks of HTML.

EJS solves this with what it calls partials. You can put the chunk of code in another file, typically in a subfolder of your views called partials. For example, we could have a file partials/navbar.ejs containing this plain HTML:

<nav>
  <ul>
     <li><a href="/">home</a></li>
     <li><a href="/about">about</a></li>
     <li><a href="/contact">contact</a></li>
     <li><a href="/order">order</a></li>
  </ul>
</nav>

Then in our template files, we can include the partial like this:

    <%- include("partials/navbar.ejs") %>

This allows us to assemble our dynamic web pages out of larger chunks of HTML, which is a big advantage.

Static Files

Our dynamic web pages may also refer to static files, such as a CSS file. In fact, our templates above all contained the following line of HTML:

    <link rel="stylesheet" href="/styles.css">

We could similarly have static files of JavaScript code (to run in the browser), image files (for logos, backgrounds, and the like) and so forth.

Since the CSS is a static file, there's no need to write a handler function or to dynamically compute anything. Instead, Express can just send the file as needed. To do that, we have the following lines in our prelude:

const serveStatic = require('serve-static');

app.use(serveStatic('public'));

We mentioned this last time, in our discussion of Express.

We then put the static files in the public folder. This folder is in our main folder, as a sibling of views. So our app structure now looks like:

app/
    connection.js -> ../connection.js
    cs304.js -> ../cs304.js
    node_modules -> ../node_modules
    public/
        styles.css
    server.js
    views/
        index.ejs
        partials/
            navbar.ejs

That's about it for the complex structure of our apps. We'll add more templates, partials and static files, but this is as many folders as we'll need, at least for a while.

All Example Files

Here are links to all the example files, instead of just snippets. All of the working files are in the ~cs304node/apps/ejs/ folder; we'll play with that in class.

Summary

  • EJS allows us to mix HTML and JavaScript in a template.
  • The res.render(template, data) method combines the data with the template to produce a page of pure HTML
  • The template can insert dynamic data using the <%= expression %> tag.
  • The template can use control structures (conditionals and loops) using <% syntax %>
  • The templates are stored in a views folder.
  • EJS also allows partials, which can pull chunks of HTML from files
  • Partials are typically stored in a views/partials folder
  • Partials are used by <%- include('path/to/partial') %>

Exhanced Object Literals

Version 6 of JavaScript (ES6) has a enhanced object literal syntax that I have often found handy.

There is a small trick, which can be convenient at times. We often have some variables, say x and y and we want to create an object literal (a dictionary) with keys matching those values. Like this:

let x = 3;
let y = 4;
let obj = {x: x, y: y}; // {x: 3, y: 4}

In that not un-common situation, we can abbreviate and just give the keys:

let x = 3;
let y = 4;
let obj = {x, y}; // {x: 3, y: 4}

This avoids a small amount of tedious typing, but also (in my opinion) makes the code a little clearer.

So, back in the section on conditionals the argument to render could just be {userId} instead of {userId: userId}