Skip to content

ExpressJS - Templating

When building web applications, you often need to generate dynamic HTML pages. Instead of manually constructing HTML strings in your server code (which is error-prone and hard to maintain), you can use a templating engine. A templating engine allows you to write static template files with special syntax to dynamically insert data on the server before sending the page to the client.

Pug (formerly known as Jade) is a popular and powerful templating engine for Node.js and browsers. It features a clean, indentation-based syntax that simplifies HTML, and it supports features like includes, inheritance, and interpolation. While we’ll focus on Pug, other popular engines like EJS (uses plain HTML with <% %> tags) and Handlebars (uses {{ }} tags) are also excellent choices.

First, you need to add Pug as a project dependency. Open your terminal in the project root and run:

npm install pug

Next, configure Express to use Pug as its view engine. You don’t need to require('pug') explicitly; Express handles it internally when you set the engine. Add these two lines to your main application file (e.g., index.js):

// Set the view engine to Pug
app.set('view engine', 'pug');
// Specify the directory where the template files are located
app.set('views', './views');

By convention, Express looks for templates in a views directory in the project root. Let’s create this directory and a simple Pug file inside it.

Create a new directory named views. Inside it, create a file named first_view.pug with the following content:

doctype html
html(lang="en")
head
meta(charset="UTF-8")
meta(name="viewport", content="width=device-width, initial-scale=1.0")
title Hello Pug
body
p.greetings#people Hello World!

Note on Modern Practices:

  • The lang="en" attribute on the <html> tag is good for accessibility and SEO.
  • The viewport meta tag is essential for responsive design on mobile devices.

Now, create a route in index.js to render this template using res.render():

app.get('/first-template', (req, res) => {
// Express will look for 'first_view.pug' in the 'views' directory
res.render('first_view');
});

Your complete index.js should now look something like this:

import express from 'express';
const app = express();
const PORT = process.env.PORT || 3000;
// Configure the view engine
app.set('view engine', 'pug');
app.set('views', './views');
// Route to render the template
app.get('/first-template', (req, res) => {
res.render('first_view');
});
app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
});

Run your server (npm run dev if you’ve set up a script with nodemon) and navigate to http://localhost:3000/first-template. You will see a page with the text “Hello World!”. Pug compiles your clean .pug file into standard HTML:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello Pug</title>
</head>
<body>
<p class="greetings" id="people">Hello World!</p>
</body>
</html>

The real power of templating comes from passing dynamic data from your route handler to the template. The second argument of res.render() is an object containing the data you want to pass.

Let’s create a new route and a corresponding template:

app.get('/profile/:username', (req, res) => {
const user = {
name: req.params.username,
isLoggedIn: true,
lastLogin: new Date(),
interests: ['Web Development', 'Node.js', 'Express']
};
res.render('profile', {
title: `${user.name}'s Profile`,
user: user
});
});
doctype html
html(lang="en")
head
title= title
body
//- This is a Pug comment. It will not appear in the HTML output.
// Use '=' to output escaped HTML. This prevents XSS attacks.
h1 Welcome, #{user.name}!
// Use 'if/else' for conditionals
if user.isLoggedIn
p Your last login was on #{user.lastLogin.toLocaleDateString()}.
else
p Please log in to see your details.
// Use 'each' for loops
if user.interests.length
h2 Your Interests:
ul
each interest in user.interests
li= interest
else
p You have no listed interests.

Now, if you visit http://localhost:3000/profile/Alice, Pug will use the user object to generate a dynamic HTML page. This technique of embedding variables directly into text is called interpolation (#{variableName}).

As seen above, you can use JavaScript-like control flow directly in your Pug templates:

  • Conditionals: if, else if, else, and unless (which is if !(...)).
  • Iteration: Use each val in array or each val, index in array to loop over arrays. You can also use while loops.

To avoid repeating code like headers and footers on every page (following the Don’t Repeat Yourself principle), Pug offers include and template inheritance (extends and block).

Using include: Create reusable components.

header
nav
a(href='/') Home
a(href='/about') About
footer
p &copy; #{new Date().getFullYear()} My Awesome App
doctype html
html(lang="en")
head
title Home Page
body
include partials/header.pug
main
h1 Welcome to the Home Page!
include partials/footer.pug

Using Layouts: A more powerful method is to define a base layout and extend it.

doctype html
html(lang="en")
head
title= title || 'My App'
link(rel='stylesheet', href='/css/style.css')
body
include partials/header.pug
main.container
block content
//- The content of child templates will be inserted here
include partials/footer.pug
extends layout.pug
block content
h1 About Us
p This is the about page for our awesome application.

Using a template engine like Pug is a form of Server-Side Rendering (SSR). This approach is excellent for:

  • Content-heavy websites: Blogs, e-commerce sites, and news portals benefit from fast initial load times and strong SEO.
  • Search Engine Optimization (SEO): Search engines can easily crawl and index pre-rendered HTML content.
  • Simplicity: For many applications, SSR is simpler to set up and manage than a full client-side framework.

The alternative is to use Express to build a REST API that sends JSON data to a separate Single-Page Application (SPA) built with a framework like React, Vue, or Angular. The SPA then handles all the rendering in the user’s browser. This is a great choice for highly interactive, application-like experiences (e.g., dashboards, social media feeds).