Templating in NodeJS using Express Pug View Engine

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 htmlhtml(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.pugblock 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.pugblock 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.