Just recently, I started rewriting coligo as I realized it quickly outgrew the static site generator I initially developed for it. I've decided to go with Node.js for the backend and EJS for the templating language so I thought it would be a great idea to write a tutorial about it!

I like that EJS keeps things simple by leaving the markup as is and sprinkling some JavaScript in there for control flow. While there are nice alternatives like Jade, I find that it's yet another abstraction and custom syntax you have to become familiar with.

With that being said, in this tutorial we'll be taking a look at how to use EJS with our Node and Express application to build a very simple blog. We'll be covering partials, looping, and passing parameters to our views as we go along.

Setting Up the Project

The project structure we will be working towards will look something like this:

Node, EJS, Express Project Structure

  • views/ will contain our EJS templates and the partials we will include
  • app.js will have our Express configuration and routes
  • package.json will maintain our dependencies (Express and EJS)

Go ahead and create and empty directory then cd into it:

mkdir node-blog && cd node-blog

and let's initialize a package.json and install both EJS and Express:

npm init -y
npm install --save express ejs

EJS Partials

Partials come in handy when you want to reuse the same HTML across multiple views. Think of partials as functions, they make large websites easier to maintain as you don't have to go and change a piece of text in every page it appears in. Instead, you define that reusable bundle of code in a file and include it wherever you need it.

Our blog will consist of a home page which lists all the blog posts and a post page which will display a single post. Our home page will look like so:

Blog Homepage

and the post page:

Blog Post

As you can see from the screenshots above, the same navigation bar and footer appear in both the home and post view. This makes them perfect candidates for partials!

Let's go ahead and create those partials. Under the views/partials/ directory create a file called navbar.ejs which will contain only the HTML for the navigation bar at the top of the home and post pages:

<!-- views/partials/navbar.ejs -->
<div class="header clearfix">
    <nav>
        <ul class="nav nav-pills pull-right">
            <li role="presentation"><a href="/">Home</a></li>
        </ul>
        <h3 class="text-muted">Node.js Blog</h3>
    </nav>
</div>

and a file called footer.ejs in that same directory:

<!-- views/partials/footer.ejs -->
<footer class="footer">
    <p>&copy; 90210 Lawyer Stuff.</p>
</footer>

Now that we have our partials defined, we can use them in our home.ejs and post.ejs templates! In EJS, any JavaScript or non-HTML syntax you include in your templates is always surrounded by <% %> delimiters (you could change these delimiters if you really wanted to).

Including a partial in EJS is quite straightforward. You use <%- include( PARTIAL_FILE ) %> where the partial file is relative to the template you use it in.

Note: The <%- %> tags allow us to output the unescaped content onto the page (notice the -). This is important when using the include() statement since you don't want EJS to escape your HTML characters like '<', '>', etc...

Let's create the homepage template in views/home.ejs and include the navbar and footer partial we just created:

<!-- views/home.ejs -->

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>Node.js Blog</title>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
    <style>
        body {
            padding-top: 20px;
            padding-bottom: 20px;
        }

        .jumbotron {
          margin-top: 10px;
        }
    </style>
</head>

<body>
    <div class="container">
        <%- include('partials/navbar') %>
        <div class="jumbotron">
            <h1>All about Node</h1>
            <p class="lead">Check out our articles below!</p>
        </div>

        <div class="row">
            <div class="col-lg-12">
                <div class="list-group">
                  <!-- loop over blog posts and render them -->
                  LIST_OF_POSTS
                </div>
            </div>
        </div>
        <%- include('partials/footer') %>
    </div>
</body>

</html>

and for the post page in views/post.ejs:

<!-- views/post.ejs -->

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>POST_TITLE | Node.js Blog</title>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
    <style>
        body {
            padding-top: 20px;
            padding-bottom: 20px;
        }
    </style>
</head>

<body>
    <div class="container">
        <%- include('partials/navbar') %>
        <div>
            <h2>POST_TITLE</h2>
            <p>by <a href="#">POST_AUTHOR</a></p>

            <p>POST_CONTENT</p>
            <hr>
        </div>
        <%- include('partials/footer') %>
    </div>
</body>

</html>

As you can see creating and including partials is very straightforward with EJS. I've intentionally left in some placeholders such as LIST_OF_POSTS, POST_TITLE, POST_AUTHOR, and POST_CONTENT so that we can take a look at how we can pass data from our Node + Express application to our views in the next section.

Rendering Views with Node, Express, and EJS

Let's put together a simple Node + Express web server to serve up our blog home page and posts that we've created so far. Create an app.js file in the root of the node-blog directory.

/* app.js */

// require and instantiate express
const app = require('express')()

// fake posts to simulate a database
const posts = [
  {
    id: 1,
    author: 'John',
    title: 'Templating with EJS',
    body: 'Blog post number 1'
  },
  {
    id: 2,
    author: 'Drake',
    title: 'Express: Starting from the Bottom',
    body: 'Blog post number 2'
  },
  {
    id: 3,
    author: 'Emma',
    title: 'Streams',
    body: 'Blog post number 3'
  },
  {
    id: 4,
    author: 'Cody',
    title: 'Events',
    body: 'Blog post number 4'
  }
]

// set the view engine to ejs
app.set('view engine', 'ejs')

// blog home page
app.get('/', (req, res) => {
  // render `home.ejs` with the list of posts
  res.render('home', { posts: posts })
})

// blog post
app.get('/post/:id', (req, res) => {
  // find the post in the `posts` array
  const post = posts.filter((post) => {
    return post.id == req.params.id
  })[0]

  // render the `post.ejs` template with the post content
  res.render('post', {
    author: post.author,
    title: post.title,
    body: post.body
  })
})

app.listen(8080)

console.log('listening on port 8080')

Some points to note about the snippet above:

  • The posts array is used to mimic a database for our blog which we will query based on the post ID
  • We use app.set('view engine', 'ejs') to tell express to use EJS as our templating engine
  • Express will automatically look inside the views/ folder for template files
  • The res.render() method is used to render the view we pass it and send the HTML to the client

Let's focus on the important parts. When the user visits the home page ('/') we want to render the home.ejs template and send the result back to the client using the res.render() method below:

// blog home page
app.get('/', (req, res) => {
  // render `home.ejs` with the list of posts
  res.render('home', { posts: posts })
})

As you can see, we are excluding the views/ part of the path because Express will default to looking in that folder. We also exclude the .ejs extension because Express is smart enough to know to look for that file type since we've already set the view engine to EJS.

The interesting part is the object we pass as the second argument to the render method. By passing in an object to the render method we can make certain data available for the templates to use. In this case we are passing in the entire posts array that we defined at the top of our app.js file which we can then access from within our templates using the posts variable.

Let's go back to our views/home.ejs template and see how we can access this posts variable and render it as a list of posts to replace the LIST_OF_POSTS placeholder:

<!-- views/home.ejs -->

...

<div class="list-group">
  <!-- loop over blog posts and render them -->
  <% posts.forEach((post) => { %>
    <a href="/post/<%= post.id %>" class="list-group-item">
      <h4 class="list-group-item-heading"><%= post.title %></h4>
      <p class="list-group-item-text"><%= post.author %></p>
    </a>
  <% }) %>
</div>

...

Since we passed in the posts array to the template, we can loop over each object (post) in the array using a simple posts.forEach() loop to render each list item.

The <%= %> is used to output the value of a JavaScript variable onto the page, which in our case are properties of each post object in the posts array.

Similarly, when a user visits a post (i.e: 'http://localhost:8080/3') specified by an id, we want to query our posts array for a post with that ID and render the post.ejs template with the contents of that specific post:

// blog post
app.get('/post/:id', (req, res) => {
  // find the post in the `posts` array
  const post = posts.filter((post) => {
    return post.id == req.params.id
  })[0]

  // render the `post.ejs` template with the post content
  res.render('post', {
    author: post.author,
    title: post.title,
    body: post.body
  })
})

Once again we pass an object with 3 properties to the render method: author, title, and body, which we will use within our views/post.ejs template to replace the POST_AUTHOR, POST_TITLE, and POST_CONTENT placeholders. Using the <%= %> tags we can output the values of these variables, like so:

<!-- views/post.ejs -->
...

<div>
    <h2><%= title %></h2>
    <p>by <a href="#"><%= author %></a></p>

    <p><%= body %></p>
    <hr>
</div>

...

You can test out the simple blog we just put together by running:

node app.js

Concluding

I hope this tutorial gave you a quick overview of how to use EJS as a templating engine for your Node and Express applications. EJS has small and concise set of features and you'll probably find yourself mostly using the ones we covered in this tutorial.

Be sure to have a look at some of the other features like client-side support and caching. Also, feel free to download the code we used in this tutorial to experiment with!