Templating is a fundamental requirement to render dynamic web pages. This is because templating encourages reusability of code. In this post, we will learn about NodeJS Templating using Express EJS. Also, we will look at how to work with Express EJS partials layout for avoiding code duplication.

If you are new to Express, check out this post on getting started with Express.

1 – Express EJS Templating Engine

EJS is one of the most versatile templating engines for the NodeJS ecosystem. In earlier posts, we looked at Express Pug and Express Handlebars integration as well. However, Pug and Handlebars fall on the opposite side of the spectrum in terms of templating philosophy.

Pug follows the minimal HTML approach and supports significant JavaScript expressions within the template. On the other hand, Handlebars template almost looks like typical HTML code. However, handlebars does not support JavaScript expressions except for basic conditional logic.

EJS aims to bridge the gap between Pug and Handlebars by combining the best of both worlds. EJS templates look a lot like HTML thereby removing the obstacle of learning a different syntax. However, it also supports a good deal of expressions within the template to write some complex JavaScript logic.

2 – Installing NodeJS Express EJS

As a first step, let us install the necessary packages for our application.

To do so, we will execute the below command:

$ npm install express ejs body-parser

Below is the package.json file for our project with all the dependencies.

{
  "name": "nodejs-express-handlebars-sample",
  "version": "1.0.0",
  "description": "NodeJS Express Handlebars Sample",
  "main": "app.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "nodemon app.js",
    "start-server": "node app.js"
  },
  "author": "Saurabh Dashora",
  "license": "ISC",
  "devDependencies": {
    "nodemon": "^1.18.3"
  },
  "dependencies": {
    "body-parser": "^1.18.3",
    "ejs": "^3.1.8",
    "express": "^4.16.3"
  }
}

Once the installation is complete, we can start with creating the actual templates.

3 – Creating the Express EJS Templates

To keep our templates, we will create a special folder views within the root of our project. For our demo application, we will create three pages:

  • Page to display a list of products in our store
  • Page to add a new product to our store
  • Lastly, we have the 404 page.

Let us first create the EJS template for displaying a list of products. We will name the template file as shop.ejs.

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title><%= pageTitle %></title>
    <link rel="stylesheet" href="/css/main.css">
    <link rel="stylesheet" href="/css/product.css">
</head>

<body>
    <header class="main-header">
        <nav class="main-header__nav">
            <ul class="main-header__item-list">
                <li class="main-header__item"><a class="active" href="/">Shop</a></li>
                <li class="main-header__item"><a href="/admin/add-product">Add Product</a></li>
            </ul>
        </nav>
    </header> 

    <main>
        <h1>My Products</h1>
        <p>List of all the products...</p>
        <% if (prods.length > 0) { %>
            <div class="grid">
                <% for (let product of prods) { %>
                    <article class="card product-item">
                        <header class="card__header">
                            <h1 class="product__title"><%=product.title %></h1>
                        </header>
                        <div class="card__image">
                            <img src="https://cdn.pixabay.com/photo/2016/03/31/20/51/book-1296045_960_720.png" alt="A Book">
                        </div>
                        <div class="card__content">
                            <h2 class="product__price">$19.99</h2>
                            <p class="product__description">A very interesting book about so many even more interesting things!
                            </p>
                        </div>
                        <div class="card__actions">
                            <button class="btn">Add to Cart</button>
                        </div>
                </article>
                <% } %>
            </div>
        <% } else { %>
            <h1>No Products Found!</h1>
        <% } %>
    </main>
</body>

</html>

As you can see, the EJS template has placeholders for dynamic content. For example, in the placeholder <%= pageTitle %>, the value of pageTitle is dynamic.

Also, EJS templates can have expressions such as <% if (prods.length > 0) { %> for determining whether the list of products should be shown or not. In case there are multiple products, we can also write loops in EJS using <% for (let product of prods) { %>.

Note that expressions in EJS templates just work like writing normal JavaScript with opening and closing parantheses.

Next, we can have an EJS template for adding products.

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title><%= pageTitle %></title>
    <link rel="stylesheet" href="/css/main.css">
    <link rel="stylesheet" href="/css/forms.css">
    <link rel="stylesheet" href="/css/product.css">
</head>

<body>
    <header class="main-header">
        <nav class="main-header__nav">
            <ul class="main-header__item-list">
                <li class="main-header__item"><a href="/">Shop</a></li>
                <li class="main-header__item"><a class="active" href="/admin/add-product">Add Product</a></li>
            </ul>
        </nav>
    </header>

    <main>
        <form class="product-form" action="/admin/add-product" method="POST">
            <div class="form-control">
                <label for="title">Title</label>
                <input type="text" name="title" id="title">
            </div>

            <button class="btn" type="submit">Add Product</button>
        </form>
    </main>
</body>

</html>

This is again pretty much like normal HTML. Apart from the navigation header, we have a form with a single input text field.

Lastly, we have the template for 404 page.

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title><%= pageTitle %></title>
    <link rel="stylesheet" href="/css/main.css">
</head> 

<body>
    <header class="main-header">
        <nav class="main-header__nav">
            <ul class="main-header__item-list">
                <li class="main-header__item"><a href="/">Shop</a></li>
                <li class="main-header__item"><a href="/admin/add-product">Add Product</a></li>
            </ul>
        </nav>
    </header> -->
    <h1>Page Not Found!</h1>
</body>

</html>

With this, we are done with creating the templates. Now it is time to configure our application to use the EJS view engine.

4 – Node Express EJS Configuration Setup

Configuring our application to use EJS as the view engine is pretty straightforward. The configuration is done within the app.js file of our application.

See below:

const path = require('path');

const express = require('express');
const bodyParser = require('body-parser');

const app = express();

app.set('view engine', 'ejs');
app.set('views', 'views');

const adminData = require('./routes/admin');
const shopRoutes = require('./routes/shop');

app.use(bodyParser.urlencoded({extended: false}));
app.use(express.static(path.join(__dirname, 'public')));

app.use('/admin', adminData.routes);
app.use(shopRoutes);

app.use((req, res, next) => {
    res.render('404', {pageTitle: "Page Not Found", path: ""})
});

app.listen(3000);

Here, we use the app.set() function to set the view engine property to ejs. Also, we set the views property to views itself. This is basically the name of the folder where our template files are located.

Apart from configuration, the app.js file adds the necessary route files for rendering the templates. At the end, we have a special handler for the 404 page. Here, we use res.render() function to render the 404.ejs template. Within the function call, we supply the dynamic pageTitle variable. The data within this object is made available inside the template for injection.

5 – Serving the Express EJS Templates

Now, we can create the Express route handlers for serving the EJS templates. For this, we will use the Express Router package.

First, let us create the handler for shop.ejs.

const path = require('path');

const express = require('express');

const rootDir = require('../util/path');
const adminData = require('./admin');

const router = express.Router();

router.get('/', (req, res, next) => {
  const products = adminData.products;
  res.render('shop', { prods: products, 
    pageTitle: 'Shop', 
    path: '/'});
});

module.exports = router;

Basically, here we again call the res.render() method. Within the method, we pass the name of the template (shop) and the dynamic data in the form of an object.

Next, we can also implement the route handler for the add-product page. See below:

const path = require('path');

const express = require('express');

const rootDir = require('../util/path');

const router = express.Router();

const products = [];

// /admin/add-product => GET
router.get('/add-product', (req, res, next) => {
  res.render('add-product', {pageTitle: 'Add Product', path: '/admin/add-product'});
});

// /admin/add-product => POST
router.post('/add-product', (req, res, next) => {
  products.push({ title: req.body.title });
  res.redirect('/');
});

exports.routes = router;
exports.products = products;

Here, we have two handlers. First is for rendering the add-product.ejs template. Second handler is for receiving the POST request for the add product form submission.

6 – Express EJS Partials

Pug and handlebars both provide ways to create reusable layouts to handle duplicate parts of our template. In EJS, we have something called partials to handle duplication of HTML code.

If you notice, in our three templates, we have a bunch of common things. There are common CSS files and a common navigation header for the three pages. Also, the overall structure of the HTML document is quite similar.

To reduce duplication, we will leverage EJS partials. To do so, we create a folder named includes within the views folder of our project. Within this folder, we create three separate EJS templates (also known as partials).

First is the template for navigation header. See below:

<header class="main-header">
    <nav class="main-header__nav">
        <ul class="main-header__item-list">
            <li class="main-header__item"><a class="<%= path==='/' ? 'active': '' %>" href="/">Shop</a></li>
            <li class="main-header__item"><a class="<%= path==='/admin/add-product' ? 'active': '' %>" href="/admin/add-product">Add Product</a></li>
        </ul>
    </nav>
</header>

This also has the logic for determining the active class based on the path property.

Next, we have a partial for the head of the HTML.

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title><%= pageTitle %></title>
    <link rel="stylesheet" href="/css/main.css">

Lastly, we have a partial for the end of the HTML document.

</body>

</html>

Now, we can use these EJS partials within our main templates.

See below the modified shop.ejs template.

<%- include('includes/head.ejs') %>
    <link rel="stylesheet" href="/css/product.css">
</head>

<body>
    <%- include('includes/navigation.ejs') %>
    <main>
        <h1>My Products</h1>
        <p>List of all the products...</p>
        <% if (prods.length > 0) { %>
            <div class="grid">
                <% for (let product of prods) { %>
                    <article class="card product-item">
                        <header class="card__header">
                            <h1 class="product__title"><%=product.title %></h1>
                        </header>
                        <div class="card__image">
                            <img src="https://cdn.pixabay.com/photo/2016/03/31/20/51/book-1296045_960_720.png" alt="A Book">
                        </div>
                        <div class="card__content">
                            <h2 class="product__price">$19.99</h2>
                            <p class="product__description">A very interesting book about so many even more interesting things!
                            </p>
                        </div>
                        <div class="card__actions">
                            <button class="btn">Add to Cart</button>
                        </div>
                </article>
                <% } %>
            </div>
        <% } else { %>
            <h1>No Products Found!</h1>
        <% } %>
    </main>
<%- include('includes/end.ejs') %>

Basically, wherever we have a common piece of the template we use the include keyword to use a partial file. For example, the statement <%- include('includes/navigation.ejs') %> brings in the template for navigation. Similarly, we use the other two partial templates.

Next, we have the modified add-product.ejs template file.

<%- include('includes/head.ejs') %>
    <link rel="stylesheet" href="/css/product.css">
</head>

<body>
    <%- include('includes/navigation.ejs') %>

    <main>
        <form class="product-form" action="/admin/add-product" method="POST">
            <div class="form-control">
                <label for="title">Title</label>
                <input type="text" name="title" id="title">
            </div>

            <button class="btn" type="submit">Add Product</button>
        </form>
    </main>
<%- include('includes/end.ejs') %>

Lastly, we have the 404.ejs file.

<%- include('includes/head.ejs') %>
</head> 

<body>
    <%- include('includes/navigation.ejs') %>
    <h1>Page Not Found!</h1>
<%- include('includes/end.ejs') %>

With this, we are done with modifying our Node Express application to use EJS partials.

We can now start the application using npm run start and look at the various pages generated using the templates.

Conclusion

NodeJS templating using Express EJS is a great way to mix both HTML and JavaScript expressions in designing robust templates. EJS also provides the facility of using partials to handle duplication of template.

The code for this demo application is available on Github along with all the CSS classes as well.

Want to create a MVC pattern application? Check out this post to create NodeJS Express MVC application from scratch.

If you have any comments or queries about this post, please feel free to mention them in the comments section below.


Saurabh Dashora

Saurabh is a Software Architect with over 12 years of experience. He has worked on large-scale distributed systems across various domains and organizations. He is also a passionate Technical Writer and loves sharing knowledge in the community.

0 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *