The view layer is the part of the MVC architecture that the user can see, so of course, it should be created with HTML.
1app.get("/", (req, res) => {
2 res.send(`<!DOCTYPE html>
3<html>
4<head>
5 <meta charset="UTF-8" />
6 <title>title</title>
7</head>
8<body>
9 . . .
10</body>
11</html>`);
12});
But, there is a problem, because in practice, we cannot hard code the entire webpage.
For example, what if we need to embed variables inside the HTML template? Or, what if we need to conditionally render a piece of HTML based on certain requirements? Essentially, we need to add some programming features to the static HTML page.
This is why we need a template engine. Think of it as a mini-framework that works inside Express. Its job is to generate a proper HTML page for the view layer based on a template.
Getting started with Pug
The default template engine for Express is Pug, which can be installed with the following command:
1npm install pug --save
Create an index.pug
file inside the views
directory.
1.
2├── controllers
3├── database.sqlite
4├── index.js
5├── models
6│ └── user.js
7├── package-lock.json
8├── package.json
9├── routes
10└── views
11 └── index.pug <===
views/index.pug
1doctype html
2html
3 head
4 title Page Title
5 body
6 p Hello, World!
This will be the template for our homepage, which will be rendered when you visit the root route (/
).
To use this template, you must inform Express where the template files are located, as well as what template engine is used.
index.js
1app.set("views", "./views"); // Defines the location of the view templates
2app.set("view engine", "pug"); // Defines the template engine
3
4app.get("/", (req, res) => {
5 res.render("index");
6});
Also notice that instead of sending the response using send()
, you must use the render()
method here. Because Express needs to render the .pug
template into an actual HTML page first, and then the rendered page will be transferred to the client.
Open Thunder Client and send a GET
request to /
. You will see the rendered HTML being returned.
Basic syntax
Now, let's take a closer look at our example index.pug
.
1doctype html
2html
3 head
4 title Page Title
5 body
6 p Hello, World!
It should look familiar to you, because they are just HTML tags, except, instead of a pair of start and end tags, Pug uses indentations. This example will render into:
1<!doctype html>
2<html>
3 <head>
4 <title>Page Title</title>
5 </head>
6 <body>
7 <p>Hello, World!</p>
8 </body>
9</html>
To include attributes, use a pair of parentheses.
1doctype html
2html
3 head
4 title Page Title
5 body
6 p(class='red bold' id='paragraph') Hello, World!
1<!doctype html>
2<html>
3 <head>
4 <title>Page Title</title>
5 </head>
6 <body>
7 <p class="red bold" id="paragraph">Hello, World!</p>
8 </body>
9</html>
Data interpolation
Data interpolation means that you can dynamically render a webpage by passing data from Express to Pug. For example,
index.js
1app.get("/", (req, res) => {
2 let name = "John";
3 res.render("index", {
4 name,
5 });
6});
The variable name
will be passed from Express to Pug (or any other template engines you want), in the form of an object. The variable can then be accessed inside Pug using #{name}
.
views/index.pug
1doctype html
2html
3 head
4 title Page Title
5 body
6 p Hello, #{name}!
And the following HTML page will be rendered:
1<!doctype html>
2<html>
3 <head>
4 <title>Page Title</title>
5 </head>
6 <body>
7 <p>Hello, John!</p>
8 </body>
9</html>
Conditional rendering
You can add flow control logics in the Pug template, which allows you to render a piece of HTML code only when certain conditions are satisfied.
index.js
1app.get("/", (req, res) => {
2 let authorized = true;
3 res.render("index", {
4 authorized,
5 });
6});
views/index.pug
1doctype html
2html
3 head
4 title Page Title
5 body
6 if authorized
7 p You are authorized to access this page.
8 else
9 p You are NOT authorized to access this page.
1<!doctype html>
2<html>
3 <head>
4 <title>Page Title</title>
5 </head>
6 <body>
7 <p>You are authorized to access this page.</p>
8 </body>
9</html>
Iterations
Loops can also be used inside the Pug template. This can be useful when you need to repeat the same piece of HTML code with only slight differences, such as a list of users, a list of articles, and so on.
Iterate over array
index.js
1app.get("/", (req, res) => {
2 let arr = ["One", "Two", "Three"];
3 res.render("index", {
4 arr,
5 });
6});
views/index.pug
1doctype html
2html
3 head
4 title Page Title
5 body
6 ul
7 each value in arr
8 li= value
Note that the equal sign (=
) cannot be left out here. It tells Pug that value
is a variable and should not be printed as a string.
1<!doctype html>
2<html>
3 <head>
4 <title>Page Title</title>
5 </head>
6 <body>
7 <ul>
8 <li>One</li>
9 <li>Two</li>
10 <li>Three</li>
11 </ul>
12 </body>
13</html>
Iterate over object
index.js
1app.get("/", (req, res) => {
2 let obj = { one: "One", two: "Two", three: "Three" };
3 res.render("index", {
4 obj,
5 });
6});
views/index.pug
1doctype html
2html
3 head
4 title Page Title
5 body
6 ul
7 each value, key in obj
8 li= key + ": " + value
1<!doctype html>
2<html>
3 <head>
4 <title>Page Title</title>
5 </head>
6 <body>
7 <ul>
8 <li>one: One</li>
9 <li>two: Two</li>
10 <li>three: Three</li>
11 </ul>
12 </body>
13</html>
each else
Lastly, you can define a special condition by adding an else
block, which will be rendered when the array or the object is empty.
index.js
1app.get("/", (req, res) => {
2 let arr = [];
3 res.render("index", {
4 arr,
5 });
6});
views/index.pug
1doctype html
2html
3 head
4 title Page Title
5 body
6 ul
7 each value in arr
8 li= value
9 else
10 li Array is empty
1<!doctype html>
2<html>
3 <head>
4 <title>Page Title</title>
5 </head>
6 <body>
7 <ul>
8 <li>Array is empty</li>
9 </ul>
10 </body>
11</html>
Inheritance
Inheritance is probably one of the most important features of Pug. For most web applications, there are certain components that should remain consistent across multiple pages, such as the navigation menu and the footer.
If you hardcode these components on multiple pages, it will become a huge trouble when you need to make some changes. And it will likely lead to mistakes as you will be jumping across different files.
A better way to do this is to use Pug's inheritance system. For example, you could have a layout.pug
file, which contains the code that should be shared on different pages.
views/layout.pug
1doctype html
2html
3 head
4 title Page Title
5 block meta
6 body
7 div Navbar
8 block content
9 div Footer
In this example, using the keyword block
, we defined two blocks, meta
and content
. They will be replaced later by the blocks defined in the children. For instance, you can make our index.pug
extend to layout.pug
.
views/index.pug
1extends layout.pug
2
3block meta
4 meta(name="description" content="Lorem Ipsum")
5
6block content
7 div This is the content
In this case, line 4 will replace the meta
block in the layout.pug
, and line 7 will replace the content
block, eventually giving you the following result.
index.js
1app.get("/", (req, res) => {
2 res.render("index");
3});
1<!doctype html>
2<html>
3 <head>
4 <title>Page Title</title>
5 <meta name="description" content="Lorem Ipsum" />
6 </head>
7 <body>
8 <div>Navbar</div>
9 <div>This is the content</div>
10 <div>Footer</div>
11 </body>
12</html>
Include
Besides the inheritance system, sometimes you might want to include a piece of HTML in certain pages but not in others, such as a sidebar. In this case, you can use the keyword include
like this:
views/sidebar.pug
1div This is the sidebar
views/index.pug
1extends layout.pug
2
3block meta
4 meta(name="description" content="Lorem Ipsum")
5
6block content
7 div This is the content
8 include sidebar.pug
1<!doctype html>
2<html>
3 <head>
4 <title>Page Title</title>
5 <meta name="description" content="Lorem Ipsum" />
6 </head>
7 <body>
8 <div>Navbar</div>
9 <div>This is the content</div>
10 <div>This is the sidebar</div>
11 <div>Footer</div>
12 </body>
13</html>
That's not all
In this lesson, we went over some of the most fundamental features of Pug, but there are still some more advanced features we did not cover, such as mixins, filters, and inline JavaScript. These features might be useful to you depending on your specific requirements, so if you are interested, you can still learn more about Pug here.