It is a common requirement to render dynamic content into our HTML page. Templating engines are a great way to support this feature. In this post, we will learn how to perform templating in NodeJS using Express Pug view engine.

In case you are new to Express, do check out this post on getting started with ExpressJS.

1 – What is a Templating Engine?

Templating engines help make our web applications dynamic in terms of data. At the heart of a templating engine, there is a HTML template with placeholders for dynamic data.

When a request is processed by our application, the templating engine kicks into action. Basically, the engine replaces the placeholders or snippets with actual HTML content. In other words, a templating engine performs on-the-fly generation of HTML that is sent to the client.

Some of the most popular templating engines are:

  • EJS
  • Pug (also known as Jade)
  • Handlebars

Each of the engines has its own philosophy for defining the placeholders. In this post, we will look at Pug and how we can use it with NodeJS and Express.

2 – NodeJS Express Pug Installation

As a first step to using Pug in our Express app, we need to install the necessary packages.

To do so, we can execute the below command:

$ npm install express pug body-parser

Below is the package.json for our example application.

{
  "name": "nodejs-express-pug-sample",
  "version": "1.0.0",
  "description": "NodeJS Express Pug 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",
    "express": "^4.16.3",
    "pug": "^3.0.2"
  }
}

As a templating engine, Pug uses minimal HTML. Instead, it leans towards a sort of custom template language.

Let us now look at building some templates using Pug.

3 – Creating NodeJS Express Pug Templates

For the purpose of our demo application, we will create a simple application that is going to show a list of products. Also, we will have a page to add products to our shop.

Let us first create the product list page template. We will keep our templates within our source code inside a special views folder. The file name will be shop.pug.

doctype html
html(lang="en")
    head
        meta(charset="UTF-8")
        meta(http-equiv="X-UA-Compatible", content="IE=edge")
        meta(name="viewport", content="width=device-width, initial-scale=1.0")
        title #{pageTitle}
        link(rel="stylesheet", href="/css/main.css")
        link(rel="stylesheet", href="/css/product.css")
    body 
        header.main-header
            nav.main-header__nav
                ul.main-header__item-list 
                    li.main-header__item
                        a.active(href="/") Shop 
                    li.main-header__item
                        a(href="/admin/add-product") Add Product 
        main 
            if prods.length > 0
                .grid 
                    each product in prods
                        article.card.product-item
                            header.card__header
                                h1.product_title #{product.title}
                            .card__image 
                                img(src="https://cdn.pixabay.com/photo/2015/11/19/21/10/glasses-1052010__340.jpg" alt="Product Image")
                            .card__content 
                                h2.product__price $9.99
                                p2.product_description The Best Book Ever Written 
                            .card__actions
                                button.btn Add To Cart
            else 
             h1 No Products

As you can see, the Pug template hardly looks like the familiar HTML syntax. Of course, we still use the common HTML tags to describe the page.

Indentation matters a lot in a Pug template. Therefore, we need to be mindful of indenting properly to communicate the hierarchy of our HTML structure.

Inside a pug template, we can also specify CSS classes as header.main-header. Here, header is the HTML tag and main-header is the CSS class.

We can also declare expression in pug templates as if prods.length > 0. Here, prods contains the list of the products in our store. Also, we can define a placeholder for dynamic piece of data in our HTML using the #{product.title}. At the time of request execution, the Pug templating engine will replace the placeholder with actual data.

Similarly, we can define a pug template for adding products.

html(lang="en")
    head
        meta(charset="UTF-8")
        meta(http-equiv="X-UA-Compatible", content="IE=edge")
        meta(name="viewport", content="width=device-width, initial-scale=1.0")
        title Add Product
        link(rel="stylesheet", href="/css/main.css")
        link(rel="stylesheet", href="/css/forms.css")
        link(rel="stylesheet", href="/css/product.css")
    body 
        header.main-header
            nav.main-header__nav
                ul.main-header__item-list 
                    li.main-header__item
                        a(href="/") Shop 
                    li.main-header__item
                        a.active(href="/admin/add-product") Add Product 
            main
                form.product-form(action="/admin/add-product", method="POST")
                    .form-control 
                        label(for="title") Title
                        input(type="text", name="title", id="title")

                    button.btn(type="submit") Add Product

Here, we have a similar structure for the header and navigation bars. However, instead of a list of products, here we are rendering a form with a single input field for product title.

4 – Creating the NodeJS Express Routes

Now that our templates our ready, we need to write the appropriate logic to serve these templates.

To do so, we will first create the app.js file for our application. Basically, this is the starting point of our app.

const path = require('path');

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

const app = express();

app.set('view engine', 'pug');
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"})
});

app.listen(3000);

The most important thing here is the configuration of pug as our view engine. We do so by using the app.set() function. Basically, the app.set() function is used to set any value globally for our application. In this case, we use it to set the view engine property to pug.

Also, we set the views property to views (basically the name of the folder where our view files are located). Incidentally, the default views folder is also views. Hence, we could have got away without setting it explicitly. However, if we decide to use a different name for the folder, we need to set it using app.set().

Next, we will create router for the shop related routes.

const path = require('path');

const express = require('express');

const adminData = require('./admin');

const router = express.Router();

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

module.exports = router;

The main thing to note in this file is the call to res.render() function. The function takes the name of the template (in this case, shop). The second attribute is an object that contains the prods property and the pageTitle property. These properties will be made available within the Pug template.

The second router is for the admin pages (or adding a product).

const path = require('path');

const express = require('express');

const router = express.Router();

const products = [];

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

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

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

Here, we have a couple of routes. One of them is a GET method handler for displaying the Add Product page. In this case, we render the add-product template.

Second is the POST method handler for adding a new product. Whenever user submits the form, we push a new product to the products array.

If interested, you can read more about routers in this post on Express Routers.

5 – NodeJS Express Pug Reusable Layout

In the above code, you may have noticed that we are repeating a bunch of template code. Basically, the navigation bar is present in both the templates even though it is practically the same. Also, we have some common CSS imports. It might benefit the maintainability of our code if we can remove this duplication of template code.

We can do so by using Pug reusable layout feature. To use this feature, we will create another folder layouts within the views folder. Inside the folder, we will create a special template file known as main-layout.pug.

html(lang="en")
    head
        meta(charset="UTF-8")
        meta(http-equiv="X-UA-Compatible", content="IE=edge")
        meta(name="viewport", content="width=device-width, initial-scale=1.0")
        title #{pageTitle}
        link(rel="stylesheet", href="/css/main.css")
        block styles
    body 
        header.main-header
            nav.main-header__nav
                ul.main-header__item-list 
                    li.main-header__item
                        a(href="/", class=(path === '/' ? 'active' : '')) Shop 
                    li.main-header__item
                        a(href="/admin/add-product", class=(path === '/admin/add-product' ? 'active' : '')) Add Product 
        block content

This is largely the same as the other templates except for the presence of special block sections. We have block styles and block content. The block keyword is an indication that the actual content for will come from some other template.

Also, since we now have a common navigation bar, we need to dynamically determine the active navigation item. For this purpose, we have some special logic while applying the class property using the incoming path property.

We can now use the main-layout reusable template in the shop and add-product templates.

extends layouts/main-layout.pug

block styles 
    link(rel="stylesheet", href="/css/product.css")

block content 
    main 
        if prods.length > 0
            .grid 
                each product in prods
                    article.card.product-item
                        header.card__header
                            h1.product_title #{product.title}
                        .card__image 
                            img(src="https://cdn.pixabay.com/photo/2015/11/19/21/10/glasses-1052010__340.jpg" alt="Product Image")
                        .card__content 
                            h2.product__price $9.99
                            p2.product_description The Best Book Ever Written 
                        .card__actions
                            button.btn Add To Cart
        else 
            h1 No Products

As you can see, here we extend the main-layout.pug using the extends keyword. Also, we define the actual content for block sections. The block sections are designated by their respective names.

Below is the revised code for the add-product.pug template file.

extends layouts/main-layout.pug

block styles 
    link(rel="stylesheet", href="/css/forms.css")
    link(rel="stylesheet", href="/css/product.css")

block content 
    main
        form.product-form(action="/admin/add-product", method="POST")
            .form-control 
                label(for="title") Title
                input(type="text", name="title", id="title")

            button.btn(type="submit") Add Product

To support the updated pug templates, we also need to pass the additional path property in our router files. This property will help determine which navigation item should get the active CSS class.

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

Conclusion

With this, we have successfully learnt how to perform templating in NodeJS using Express Pug view engine. We also looked at creating reusable pug templates to reduce duplication of code within our templates.

The code for the above sample application is available on Github. All the CSS files and classes used in this application are present in the Github repo for reference.

Want to use a different templating engine? Check out this post on NodeJS templating with Express Handlebars.

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 *