Before Single Page Applications became a thing, templating languages like Pug were super popular because they allowed developers to render a page on the server side before sending it over to the client. Express is the most popular backend application framework for Node.js. It prides itself on being lightweight, unopinionated, and minimal to use. In this guide, you will learn how to serve dynamic HTML with Pug from an Express.js server application.
How does Pug work?
HTML can be cumbersome to write sometimes. The language has no support for features like "components", which can lead to code duplication unless you rely on external tools like JavaScript.
Pug is a templating engine that makes it easier to write HTML. With Pug, you can split your code and reuse "components" in as many places as you'd like. Regarding syntax, Pug differs from traditional HTML as it uses indentation instead of closing tags. In HTML, you define an element like this:
<div class='hello'>This is something worth noting</div>
In Pug, however, you define an element like this:
div(class='hello') This is something worth noting
The tag name is defined on the left, with its attributes in brackets. The tag is separated from its contents by the space next to it. The Pug transpiler will transpile your code back to the proper HTML code recognized by the browser. Child elements are defined by indentation. This means if you wanted to have a div
inside a main
tag, you'd do something like this:
main
div Hello from the children of planet Earth!
Integrating Pug with Express
To add Pug to your Express.js project, simply install Pug with any package manager of your choice. For this example, I'm working with NPM:
npm i pug
This will add Pug to your list of dependencies in your package.json
file. Now, you'll need to set your view engine to pug, so in your project's entry file (usually main.js
, app.js
, or index.js
), import express properly and configure the application settings with the set
method.
import express from 'express';
const app = express();
app.set('view engine', 'pug');
By setting view engine
to 'pug', you're telling Express to use Pug as its templating engine. So, when you call the render
method on the response
object, you must pass a valid 'view' for Express to render. Views in Express must be placed in a special views
directory, in the project's root directory. If you haven't created a views
directory, you can do so with the following:
mkdir views
# Make sure you are in your project root
Now, that you've got things set up, let's proceed to writing our first view in Pug.
Basic Pug Usage in an Express Project
Create a views/index.pug
file, and add the following to it:
html
head
title Welcome to my website
body
div Hello World
Now that your index.pug
file is ready, you'll need to serve it to the client on a route. Go to your project's entry file and define a get request handler that will render and return the views/index.pug
file to the client.
app.get("/", (req, res) => {
res.render("index.pug");
});
When you open localhost:<your-port>
, you should see the "Hello World" message printed on the screen. In the callback function of the get
method, you'll see the usage of res.render
in its simplest form. The syntax for the render
method is shown below:
res.render(view [, locals] [, callback]);
First, we have view
, which is simply the path of the .pug
file you want to render. Remember that Express locates the .pug files relative to the views directory. So, if you have a Pug file located at views/layouts/main.pug, you should refer to it as layouts/main when setting the view in your route.
app.get("/", (req, res) => {
res.render("layouts/main");
});
Next, the locals
is an object with properties that define local variables that should be passed into the specified view for interpolation. When callback
is provided, the resulting HTML from the render operation is not sent to the client. Instead, you can access it via a parameter in the callback function, like this:
app.get("/", (req, res) => {
res.render("index.pug", {}, (err, html) => {
console.log(html);
});
});
When the client makes a get request to '/', a response is not sent. Instead, html
is logged to the server console. You can manually send the HTML to the client with the send
method:
res.send(html)
Building Dynamic HTML Pages
Now it's time to take things to the next level. You'll learn how to interpolate data with Pug to create dynamic content on the fly. In Pug, string interpolation is done with the syntax #{<item>}
. At compilation time, #{<item>}
is resolved to its actual value. Here's an example.
// greet.pug
html
head
title Welcome to my website
body
div Hello #{name}
In the code block above, name
will be replaced the actual value from the locals
object passed into the render
method. If name
is undefined, no error is thrown. Here it is in action.
app.get('/greet', (req, res) => {
const {name} = req.query;
res.render('greet.pug', {name})
})
When the client hits /greet?name=David
, the following HTML will be returned
<html>
<head>
<title>Welcome to my website</title>
</head>
<body>
<div>Hello David</div>
</body>
</html>
The string interpolation syntax (#{}
), is escaped by Pug. This is useful in situations where the content comes from users. If you want Pug is render the string as is without escaping, you'll need to use the !{}
syntax.
- var example = <strong>very risky</strong>
div !{example}
Tags and Tag Intepolation in Pug
Pug provides a handy syntax for tag interpolation #[]
, which you can use like this:
- Basic Tag Interpolation: You can interpolate a tag directly within text.
p This is a #[strong very important] message.
This will render as:
<p>This is a <strong>very important</strong> message.</p>
- Interpolating with Variables: You can also interpolate tags with variables.
- var username = 'John'
p Hello, #[strong #{username}]!
You don't have to worry about self-closing tags, because Pug knows what tags are self closing. But if you really need to self-close a tag, you can append the /
character to the end of the tag like this:
div/
To save space, You can use the :
shorthand instead of indentation to specify nested tags.
label: input(type='text' name='username')
The code block above is just as valid as:
label
input(type='text' name='username')
Using JavaScript in Pug
In the last code block, notice the use of the var
keyword from JavaScript to create a variable. Pug allows you to insert valid JavaScript code on any line that starts with an -
. For example, you can create an array and iterate over it to render a list of items. Pug has its native syntax for this, but in this example, you can use JavaScript.
html
head
title Welcome to my website
body
div List of items
- var items = ['milk', 'peas', 'bread']
- items.forEach((item)=>{
li #{item}
- })
Study the previous example. Notice how Pug and JavaScript are combined. The forEach
method is not part of the Pug API, it belongs to JavaScript. Likewise, the string interpolation symbol is not part of the #{}
JavaScript API. The lines with valid JavaScript code are marked with the -
symbol. On the second to last line, there is no -
symbol, because that is Pug code.
Iteration and conditionals with Pug
For common things like conditionals and iteration, Pug provides its syntax that you can use instead of JavaScript. The most popular keyword for iteration in Pug is each
. each
must come in the form each VARIABLE_NAME of JS_EXPRESSION
. Here's how you can use it:
each item in ['milk', 'peas', 'bread']
li #{item}
When dealing with objects, the expected format for each
is each VALUE, KEY OF JS_EXPRESSION
. For example:
each val, key in {1:'milk', 2:'peas', 3:'bread'}
#{key} : #{val}
You can use the if
syntax to handle conditionals. Here's an example:
╴ var loggedIn = false
if !loggedIn
p Sorry you cannot access this item because you're not logged in
Conversely, Pug has an unless
keyword that you can use like this:
unless loggedIn
p Sorry you cannot access this item because you're not logged in
Advanced Techniques with Pug
Pug offers many features beyond just string interpolation and conditionals. If you are working on a large website, you might need to use advanced features that Pug provides, such as layouts and partials.
Using Layout files for consistent page structure
Layout files allow you to define a common structure for your pages and extend it in other templates, ensuring consistency across your website. Here's an example of how you can use layout files.
//- views/layout.pug
html
head
title My Website Title
body
header
h1 My Website
block content
footer
p Footer content
Notice the block
keyword in the code block above. A block
in a layout file acts as a placeholder. Each block
must have a name. In this example, block
is defined as content
. Whenever you want to use your layout file, you use the extends
syntax to tell Pug that a template should include a layout.
//- views/index.pug
extends layout
block content
p Welcome to the homepage!
In this example, index.pug
extends the layout.pug template, which provides the page's base structure, including the header and footer. The block content line defines a block named content where the indented paragraph "Welcome to the homepage!" is inserted. When index.pug
is rendered, the final HTML will look this this:
<html>
<head>
<title>My Website Title</title>
</head>
<body>
<header>
<h1>My Website</h1>
</header>
<p>Welcome to the homepage!</p>
<footer>
<p>Footer content</p>
</footer>
</body>
</html>
Using Partials for Reusable Components
Partials are reusable pieces of templates that can be included in other templates, which helps to keep your code DRY (Don't Repeat Yourself). You can create partials in Pug with the include
syntax.
//- views/partials/sidebar.pug
aside
p This is the sidebar content.
In sidebar.pug defines a partial template for a sidebar with an aside element containing a paragraph of text.
//- views/layout.pug
html
head
title My Website Title
body
include partials/sidebar
block content
footer
p Footer content
In layout.pug, a layout template is created with a basic HTML structure. It includes the header and sidebar partials using the include
keyword, places a block content placeholder for dynamic content, and adds a footer with a paragraph of text. The final render should look something like this:
<html>
<head>
<title>My Website Title</title>
</head>
<body>
<header></header>
<aside>
<p>This is the sidebar content.</p>
</aside>
<p>Welcome to the homepage!</p>
<footer>
<p>Footer content</p>
</footer>
</body>
</html>
Tips for optimizing Pug templates
1. Use partials and layouts wherever you can: Using partials, layouts, and helpers in Pug enhances template organization and efficiency. Partials are reusable snippets that prevent code repetition, while layouts provide a consistent structure for pages by defining common elements and extending them in individual templates.
2. Minimize the use of inline JavaScript: When writing your templates, try to use inline JavaScript sparingly. Adding huge blocks of JavaScript to your code can create issues with debugging and maintainability.
One way to reduce inline JavaScript is through the use of helpers. Helpers, defined in the server-side code, allow dynamic content within templates. You can pass a helper function to a template using the locals
method on the express
app.
const express = require('express');
const app = express();
app.set('view engine', 'pug');
app.locals.formatDate = function(date) {
return new Date(date).toLocaleDateString();
};
app.get('/', (req, res) => {
res.render('index', { title: 'Home', currentDate: new Date() });
});
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
With the formatDate
helper function set, you can use it in your Pug template like this:
p Welcome to the homepage!
p Today's date is #{formatDate(currentDate)}
Conclusion
In this guide, you learned how to serve dynamic HTML with Pug and Express. We covered basic Pug syntax, integrating Pug with Express, building dynamic pages, and advanced techniques like using layout files and partials.
Templating engines are very powerful especially when building a server-side web application. They are great for Search Engine optimization too because unlike single-page applications, the content is rendered on the server on each request.