Views in Express.js Explained – Static vs Dynamic Views
A view is a template or document that defines the content and structure of a webpage returned to users by an Express.js application. It can be a static HTML file or a dynamic template rendered into HTML at runtime by a template engine.
In this guide, we’ll use a simple project to learn how Express views work. There are four main steps:
- Create the views.
- Listen for client requests.
- Analyze the client’s request.
- Respond by serving the appropriate rendered view to the client.
Let’s get started by opening your terminal and making a new directory for your project.
Create a New Project Directory
Section titled “Create a New Project Directory”Use the mkdir command to create your project directory:
mkdir codesweetly-expressjs-views-guide-001Navigate to the new project directory using your command line.
cd path/to/codesweetly-expressjs-views-guide-001Create a package.json File
Section titled “Create a package.json File”Once you’re in the project folder, use NPM to create a package.json file.
npm init -yNext, open the package.json file and delete the main field.
Configure the Project as an ES Module Application
Section titled “Configure the Project as an ES Module Application”This guide uses ES Modules in all JavaScript files, so update the type field in your project’s package.json file to module.
{ "scripts": { "test": "echo "Error: no test specified" && exit 1" }, "type": "module"}Install Express
Section titled “Install Express”npm install expressTypes of Views in Express.js
Section titled “Types of Views in Express.js”There are two types of views in Express.js:
- Static Views
- Dynamic Views
What Are Static Views in Express.js?
Section titled “What Are Static Views in Express.js?”Static views contain the final HTML that the server sends to the browser as-is. For example, let’s configure your Express.js application to serve four static views.
Create static views
Section titled “Create static views”Create index.html, about.html, contact.html, and 404.html files in your project’s root directory.
touch index.html about.html contact.html 404.htmlOpen each view and add the following HTML content:
index.html (homepage)
Section titled “index.html (homepage)”<!doctype html><html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Static Views in Express.js | CodeSweetly Tutorial</title> </head> <body> <h1>Welcome to the Static View Guide</h1> <p>Site's pages:</p> <ul> <li><a href="/about">About</a></li> <li><a href="/contact">Contact</a></li> </ul> </body></html>about.html (about page)
Section titled “about.html (about page)”<!doctype html><html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>About Us | Static Views in Express.js</title> </head> <body> <h1>About Us</h1> <p> Lorem ipsum dolor sit amet consectetur adipisicing elit. Libero soluta voluptas reprehenderit minus veniam! Corrupti a esse quidem nostrum harum, explicabo tempore tempora aut, sint et voluptatem magni ea vel? </p> <div><a href="/">Return to the home page</a></div> </body></html>contact.html (contact page)
Section titled “contact.html (contact page)”<!doctype html><html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Contact | Static Views in Express.js</title> </head> <body> <h1>Contact Us</h1> <ul> <li><a href="https://codesweetly.com">Website</a></li> <li><a href="https://x.com/oluwatobiss">X (Twitter)</a></li> </ul> <div><a href="/">Return to the home page</a></div> </body></html>404.html (error page)
Section titled “404.html (error page)”<!doctype html><html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>404 - Page not found | Static Views in Express.js</title> </head> <body> <h1>Page not found</h1> <div>Sorry, we couldn't find that page</div> <div><a href="/">Go back to the home page</a></div> </body></html>Next, build a web server for your project to handle browser connections.
Configure a web server to serve static views
Section titled “Configure a web server to serve static views”Create an index-001.js file that sets up your app to handle client requests.
touch index-001.jsOpen the module and configure the web server to serve the appropriate static HTML view in response to requests.
// Add the required modulesimport express from "express";import { dirname, join } from "node:path";import { fileURLToPath } from "node:url";
// Create a new Express application instanceconst app = express();
// Specify the hostname and port to receive requestsconst host = "localhost";const port = 3000;
// Get the directory name of the current file's pathconst __dirname = dirname(fileURLToPath(import.meta.url));
// Use the index.html static view as a response to 'GET /' requestsapp.get("/", (req, res) => res.sendFile(join(__dirname, "index.html")));
// Use the about.html static view as a response to 'GET /about' requestsapp.get("/about", (req, res) => { res.sendFile(join(__dirname, "about.html"));});
// Use the contact.html static view as a response to 'GET /contact' requestsapp.get("/contact", (req, res) => { res.sendFile(join(__dirname, "contact.html"));});
// Use the 404.html static view as a response to 404 requestsapp.use((req, res) => { res.status(404).sendFile(join(__dirname, "404.html"));});
// Run the server on the specified port and hostnameapp.listen(port, host, () => { console.log(`Server running live at http://${host}:${port}`);});Here are the main things the snippet above does:
- Create an Express application instance to access Express APIs, such as routers and middleware, which simplify HTTP request handling in Node.js.
- Get the directory name of the current file’s path.
dirname()returns a path’s directory name.fileURLToPath()converts a URL to a valid path string.import.meta.urlreturns the absolute URL of the current file.
- Create routes to receive and respond to clients’ requests.
appis an instance of the Express server.- The
app.get()Express.js route defines how the server handles HTTP GET requests to the specified path (the method’s first argument). - The
res.sendFile()method sends a file from the server to the client. join()joins multiple path segments into a single, normalized, cross-platform compatible path string.- The
app.use()method lets you set up handler functions that Express runs for all HTTP request methods and paths. If you specify a path argument,app.use()treats it as a prefix for matching routes. For example,/bookmatches/book,/book/dashboard, and/book/dashboard/author/202605. res.status(404)sets an HTTP error status code for the wildcard path’s response.
- Use Express’s
app.listen()method to start the Node.js HTTP server on a specific port and execute a callback when the server begins listening for client requests.
Run your Express.js application
Section titled “Run your Express.js application”Once you’ve set up the server, run it with Node.js.
node --watch index-001.jsAfterward, check your app running live at http://localhost:3000.
To keep things organized, put all your static views in a public directory. This is a common practice.
Create a directory for static views
Section titled “Create a directory for static views”Create a public directory at your project’s root to store all static views.
mkdir publicMove all static views into the public folder
Section titled “Move all static views into the public folder”mv index.html about.html contact.html 404.html public/Update the server to retrieve static views from the public folder
Section titled “Update the server to retrieve static views from the public folder”import express from "express";import { dirname, join } from "node:path";import { fileURLToPath } from "node:url";
const app = express();const host = "localhost";const port = 3000;const __dirname = dirname(fileURLToPath(import.meta.url));
// Serve static assets (HTML, CSS, images) from the 'public' folderapp.use(express.static(join(__dirname, "public")));
// Use the index.html static view as a response to 'GET /' requestsapp.get("/", (req, res) => { res.sendFile(join(__dirname, "/public/index.html"));});
// Use the about.html static view as a response to 'GET /about' requestsapp.get("/about", (req, res) => { res.sendFile(join(__dirname, "/public/about.html"));});
// Use the contact.html static view as a response to 'GET /contact' requestsapp.get("/contact", (req, res) => { res.sendFile(join(__dirname, "/public/contact.html"));});
// Use the 404.html static view as a response to 404 requestsapp.use((req, res) => { res.status(404).sendFile(join(__dirname, "/public/404.html"));});
// Run the server on the specified port and hostname:app.listen(port, host, () => { console.log(`Server running live at http://${host}:${port}`);});Let’s now discuss dynamic views.
What Are Dynamic Views in Express.js?
Section titled “What Are Dynamic Views in Express.js?”Dynamic views contain placeholders that template engines, such as EJS, replace with data at runtime.
The most popular Express.js template engines are EJS, Pug, and Handlebars. Let’s use EJS as an example to discuss how dynamic views work.
What is EJS?
Section titled “What is EJS?”EJS (Embedded JavaScript templating) is a templating language that lets you create dynamic HTML views by embedding JavaScript code within HTML templates using template tags.
When the server renders a template, the EJS engine executes the embedded JavaScript and generates the final HTML sent to the client.
For instance, consider the following code:
// Add the Express and EJS modulesimport express from "express";import ejs from "ejs";
// Create a new Express application instanceconst app = express();
// Specify the host and port to receive requestsconst host = "localhost";const port = 8000;
// Define the data objectconst friendsArray = ["Sarah", "Abraham", "Mary"];const replacementObject = { friends: friendsArray };
// Use backticks to define a template string with embedded JavaScriptconst templateString = ` <h1>Hello, <%= friends.join(", "); %>!</h1> <p>Welcome to CodeSweetly.</p>`;
// Compile and render the template string to HTML (the rendered view)const html = ejs.render(templateString, replacementObject);
// Use the dynamic view as a response to 'GET /' requestsapp.get("/", (req, res) => res.send(html));
// Run the server on the specified port and hostname:app.listen(port, host, () => { console.log(`Server running live at http://${host}:${port}!`);});The above snippet tells Express to send the rendered view to the client when users request the / path.
At runtime, EJS executes the embedded JavaScript code (friends.join(", ")) and replaces each friends instance in the EJS template tag with friendsArray’s value.
What are EJS template tags?
Section titled “What are EJS template tags?”EJS template tags are special tags written using the <% ... %> syntax. They allow JavaScript code and values to be embedded into EJS view templates.
When EJS renders the template, it executes the JavaScript inside these tags to generate HTML.
For example, the <%= %> tags below allow you to embed a JavaScript friend variable into the view template.
<h1>About <%= friend %>, my special pal</h1>Types of EJS template tags
Section titled “Types of EJS template tags”The nine (9) types of EJS template tags are as follows:
Closing tag (%>)
Section titled “Closing tag (%>)”Use the closing tag to end an EJS tag. It has no special behavior on its own.
Scriptlet tag (<%)
Section titled “Scriptlet tag (<%)”Use the scriptlet tag to run JavaScript code without outputting anything into the generated HTML. This tag is commonly used for control-flow, such as if, for, and forEach logic.
<ul> <% friends.forEach(function () { %> <li>My friend</li> <% }) %></ul>The snippet above wrapped <% %> around the control-flow syntax to indicate to EJS that the JavaScript code is only for logic and should not be rendered to the generated HTML.
Escaped output tag (<%=)
Section titled “Escaped output tag (<%=)”Use the escaped output tag to evaluate JavaScript expressions and output the result into the generated HTML while escaping HTML characters. This tag helps prevent HTML injection.
<%= 100 + 200 %>The above snippet wrapped <%= %> around the arithmetic expression to tell EJS to render the code’s value into the generated HTML.
Unescaped output tag (<%-)
Section titled “Unescaped output tag (<%-)”Use the unescaped tag to evaluate JavaScript expressions and output the result into the generated HTML without escaping HTML characters.
<%- "<h1>About CodeSweetly</h1>" %>The snippet above wrapped <%- %> around the H1 element to tell EJS to render the expression without escaping the HTML elements.
Literal opening tag (<%%)
Section titled “Literal opening tag (<%%)”Use the literal tag to output a literal <% sequence instead of treating it as an EJS tag. This is useful when you want to show EJS syntax in the generated output.
<%% "<p><strong>Name:</strong> <em>Oluwatobi</em></p>" %>The above snippet wrapped <%% %> around the paragraph element to tell EJS to render the expression’s EJS and HTML syntax. So, the HTML generated will look as follows:
<% "<p><strong>Name:</strong> <em>Oluwatobi</em></p>" %>Whitespace-trimmed ending tag (-%>)
Section titled “Whitespace-trimmed ending tag (-%>)”Use the Whitespace-trimmed ending tag to remove the newline immediately after the closing tag.
<ul> <% friends.forEach(function (friend) { %> <li><%= friend -%></li> <% }) %></ul>The snippet above wrapped <%= -%> around the friend variable to tell EJS to trim any newline following the closing EJS tag.
Whitespace slurp opening tag (<%_)
Section titled “Whitespace slurp opening tag (<%_)”Use the whitespace slurping tag to remove whitespace before the opening EJS tag.
<ul> <%_ friends.forEach(function (friend) { %> <li><%= friend %></li> <%_ }) %></ul>The snippet above wrapped <%_ %> around the control-flow syntax to tell EJS to trim any whitespace before the template tags.
Whitespace slurp closing tag (_%>)
Section titled “Whitespace slurp closing tag (_%>)”Use the whitespace slurping ending tag to remove whitespace after the closing tag.
<ul> <% friends.forEach(function (friend) { _%> <li><%= friend %></li> <% }) _%></ul>The snippet above wrapped <% _%> around the control-flow syntax to tell EJS to trim any whitespace after the template tag.
Comment tag (<%#)
Section titled “Comment tag (<%#)”Use the comment tag to add EJS comments that are ignored during rendering and do not appear in the generated HTML.
<ul> <%# Loop through the friends array %> <% friends.forEach(function (friend) { _%> <li><%= friend %></li> <% }) _%></ul>The snippet above wrapped the comment in <%# %>.
How to use EJS in an Express project
Section titled “How to use EJS in an Express project”The following sections will guide you through the process of using EJS in your Express.js project.
Install EJS
Section titled “Install EJS”First, install EJS in your Express project.
npm install ejsNext, build a second web server to serve dynamically generated views.
Configure a web server to serve dynamic views
Section titled “Configure a web server to serve dynamic views”Create an index-002.js file that sets up your app to handle client requests.
touch index-002.jsOpen the module and configure the web server to serve the appropriate dynamically generated HTML view in response to requests.
// Add the Express and EJS modulesimport express from "express";import ejs from "ejs";
// Create a new Express application instanceconst app = express();
// Specify the host and port to receive requestsconst host = "localhost";const port = 8000;
// Define the data objectconst friendsData = { bestFriend: "Sarah", boyFriend: "Abraham", dreamFriend: "Mary",};const replacementObject = { friends: friendsData };
// Use backticks to define a template string with embedded JavaScriptconst templateString = `<html> <body> <h1>List of Friends</h1> <ul> <%# Loop through the friends object %> <% for (const eachFriend in friends) { -%> <li><%= friends[eachFriend] %> is my <%= eachFriend %></li> <% } -%> </ul> </body></html>`;
// Compile and render the template string to HTML (the rendered view)const htmlData = ejs.render(templateString, replacementObject);
// Use the dynamic view as a response to 'GET /' requestsapp.get("/", (req, res) => res.send(htmlData));
// Run the server on the specified port and hostname:app.listen(port, host, () => { console.log(`Server running live at http://${host}:${port}!`);});Here are the main things the snippet above does:
- Create an Express application instance to access Express APIs, such as routers and middleware, which simplifies HTTP request handling in Node.js.
- Use the template string to generate the final HTML that the server sends to the client.
- Create routes to receive and respond to clients’ requests.
- Use Express’s
app.listen()method to start the Node.js HTTP server on a specific port and execute a callback when the server begins listening for client requests.
Run the Express app
Section titled “Run the Express app”After setting up the server, run it with Node.js.
node --watch index-002.jsAfterward, check your app running live at http://localhost:8000.
To keep things organized, put all your view templates in a views directory. This is a common practice.
Create a directory for view templates
Section titled “Create a directory for view templates”Create a views directory at your project’s root to store all view templates.
mkdir viewsCreate an index view template
Section titled “Create an index view template”touch views/index.ejsOpen the file and move the HTML template from index-002.js into it.
<html> <body> <h1>List of Friends</h1> <ul> <%# Loop through the friends object %> <% for (const eachFriend in friends) { -%> <li><%= friends[eachFriend] %> is my <%= eachFriend %></li> <% } -%> </ul> </body></html>Update the server to retrieve view templates from the views folder.
Section titled “Update the server to retrieve view templates from the views folder.”import express from "express";import { dirname, join } from "node:path";import { fileURLToPath } from "node:url";
const app = express();const host = "localhost";const port = 8000;const __dirname = dirname(fileURLToPath(import.meta.url));
// Specify the app's views directoryapp.set("views", join(__dirname, "views"));
// Specify the app's view engineapp.set("view engine", "ejs");
// Define the data objectconst friendsData = { bestFriend: "Sarah", boyFriend: "Abraham", dreamFriend: "Mary",};const replacementObject = { friends: friendsData };
// Compile and render the index view template to HTML and use it as a response to 'GET /' requestsapp.get("/", (req, res) => { res.render("index", replacementObject);});
// Run the server on the specified port and hostname:app.listen(port, host, () => { console.log(`Server running live at http://${host}:${port}`);});The snippet above uses Express’ render() method to generate HTML from the index.ejs template file.
How to create reusable EJS templates (Partials)
Section titled “How to create reusable EJS templates (Partials)”EJS provides the include() method for including (nesting) one view template into another.
Syntax of the include() method
Section titled “Syntax of the include() method”The include() method accepts two arguments. Here’s the syntax:
<%- include("path/to/partial", data) %>"path/to/partial": (required) The path to the reusable template, relative to the current file (the parent template). For example, if the current file is at"./views/index.ejs"and the partial is at"./views/partials/footer.ejs", theinclude()path argument would be"partials/footer".data: (optional) An object containing the properties to pass to the partial.
How to include a partial in a view template
Section titled “How to include a partial in a view template”Consider the following server file:
import express from "express";import { dirname, join } from "node:path";import { fileURLToPath } from "node:url";
const app = express();const host = "localhost";const port = 8080;const __dirname = dirname(fileURLToPath(import.meta.url));
app.set("views", join(__dirname, "views"));app.set("view engine", "ejs");
const friendsData = { bestFriend: "Sarah", boyFriend: "Abraham", dreamFriend: "Mary",};const replacementObject = { friends: friendsData };
app.get("/", (req, res) => { res.render("index", replacementObject);});
app.listen(port, host, () => { console.log(`Server running live at http://${host}:${port}`);});The snippet above will render an index template when users send a GET request to the "/" path. Below is the index.ejs template file.
<html> <body> <h1>List of Friends</h1> <ul> <%# Loop through the friends object %> <% for (const eachFriend in friends) { -%> <li><%= friends[eachFriend] %> is my <%= eachFriend %></li> <% } -%> </ul> </body></html>If you need the <li> element to be reusable in multiple templates, extract it into a separate file and use the include() method to add it to any template as needed. Here’s how:
Create a partials directory
Section titled “Create a partials directory”Create a partials directory in your views folder to store all reusable view templates.
mkdir views/partialsCreate a partial view template
Section titled “Create a partial view template”Create a partial view template for the <li> element.
touch views/partials/friendLi.ejsOpen the file and add the <li> element.
<li><%= friend %> is my <%= friendType %></li>Update the main view template
Section titled “Update the main view template”Open the index.ejs file and use the include() method to add the friendLi.ejs partial view template.
<html> <body> <h1>List of Friends</h1> <ul> <%# Loop through the friends object %> <% for (const eachFriend in friends) { -%> <%- include("partials/friendLi", { friend: friends[eachFriend], friendType: eachFriend }) %> <% } -%> </ul> </body></html>Using include() to add the <li> element makes it reusable in multiple templates, rather than restricting it to the index.ejs view template.
Run your server from the root directory
Section titled “Run your server from the root directory”node --watch index-003.jsThen, check your app running live at http://localhost:8080.