Essentials when using EJS with Express
EJS, or, "Embedded JavaScript" is a template language. Templating languages help us write "view" or output files that look like the target language, but can include any back-end programming where we may need it. You can switch between the two when needed, usually using some sort of special characters.
In this article, we'll look into core concepts you'll want to be familiar with when using EJS as your templating language in a Node.js Express application. If you don't have Node.js installed, consider trying this side dish. If you have never set-up a Node.js Express application before, please first enjoy our main dish before moving on to this course.
The main features we'll dive into here include:
- Creating and Using EJS Views
- Using Back-end JS in a Template
- Pass Values to Templates using Template Variables
- Break Down Templates into Smaller, Re-usable "Partials"
Creating and Using EJS Views
This section will largely be review if you've sampled our Express main course, albeit a quick one! So let's get started.
Project Set-Up
Navigate to wherever you like to keep your practice projects, and create a new directory for today's experiments. Enter the new project directory and initialize it as an npm project.
# Enter your user directory
cd ~
# Enter your project directory (yours will probably be different! Make one if you don't have one)
cd projects
# Create a new directory for this project
mkdir ejs-and-express
# Enter the directory
cd ejs-and-express
# Initialize npm so we can easily keep track of packages (dependencies)
npm init -y
Now we're in a good place to begin an Express project and install EJS.
# You can install Express and EJS in one line!
npm install express ejs --save
We need two files to start off our experiment:
- views/index.ejs - Our EJS template (we want to be able to write HTML here and ultimately use Express to send it to a client.)
- index.js - Our Express application (it will listen for requests and is able to send template output as a response.)
views/index.ejs
Let's start with the template. It won't do anything on its own, but that's okay! We'll have something to work with in a moment when we get to our Express application's logic.
# Create a directory: "views", your templates will go here
mkdir views
# Create a template file, we'll add template code here
touch views/index.ejs
On the web, templates will usually be HTML output, so that's what we'll place in this file. We'll make it a very basic hello page for now, but we can come back and add to this later if we'd like! Add the following HTML code into your /views/index.ejs template file:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Express Application with EJS Template</title>
</head>
<body>
<h1>Express Application with EJS Template</h1>
<p>Welcome to my Express application! This application is currently displaying the result of an EJS template.</p>
</body>
</html>
index.js
Now we'll create a file for our Express application logic:
# Create index.js
touch index.js
Let's write the necessary code for our Express application to display our template in our new index.js:
// Bring Express into our file
const express = require('express');
// Choose a port you'd like to use to access your world wide web site
const PORT = 3333;
// Initialize Express and store the result in 'app'
const app = express();
// Set our view engine to "ejs" so that Express will expect and understand EJS template files
app.set('view engine', 'ejs');
// Listen for traffic on your select port
app.listen(PORT, function() { console.log(
// Print a message to terminal as a reminder where you can visit your site when the app is running
`Express application is now available at: http://localhost:${PORT}`
) });
// Create a route; when a client (web browser) visits this page, the function we write will run
app.get('/', function(request, response) {
response.render('index'); // Will run: "views/index.ejs" (we don't have to mention /views or the extension, .ejs)
});
When using EJS with Express, these two lines are essential:
- app.set('view engine', 'ejs'); - Express won't expect or understand EJS files unless you have run npm install ejs --save and included the app.set call.
- response.render('TEMPLATE_FILE_NAME') - This tells Express with file in your /views directory should be used as the template for this route response. Express expects templates to live inside of the /views directory, so we don't have to mention it. Because of app.set we don't need to mention the file's extension (.ejs), either, as it is already specified there.
If you'd like to customize which directory in the project is used for storing views, this can be customized by using the following (but the default is recommended):
app.set('views', 'your-own-view-directory-name'); // Not recommended, but the option is there if you need it!
We should now be able to run the application. Let's give it a shot! In your terminal, run:
node index.js
Once the application is running, visit the following in your web browser: http://localhost:3333/ — and there it is!
Using Back-end JS in a Template
Let's create a template that uses a bit of back-end logic. When someone says "back-end logic," we're saying that the JavaScript code will run in your application—on the server—and that the logic code will not be sent to the client (web browser.)
Again, we'll start with a template file. This time I'll call it views/programming-languages.ejs. This file will look similar to our last template, but we'll be introducing a new syntax.
EJS "Tags," <% %>
When EJS reads your templates, it effectively treats everything in the file like a string that is ready to be sent to the client. There is a powerful exception, however! You can exit HTML and write logic at any time in your templates using an EJS tag: <% %>. Anything between these tags will be executed as a JavaScript expression in your application, instead of being sent directly to the client as text characters.
Make note of the EJS tag options:
Opening Tag
Each of these mark the beginning of a back-end logic section of your template.
- <% - No output, just logic!
- <%_ - Removes any preceding white-space; no output, just logic!
- <%= - Outputs result of the expression, escaping any HTML characters (good if you don't want to create HTML elements with this expression.)
- <%- - Outputs result of the expression, will not escape HTML elements (good if you do want to create HTML elements with this expression.)
- <%# - Comment—no logic, no output!
- <%% - Outputs literal characters: <%
Closing Tag
Each of these mark the beginning of a back-end logic section of your template.
- %> - General-use ending tag, most-common! Use this if you're not sure.
- -%> - Removes the "new-line" character that follows the tag.
- _%> - Removes all white-space characters that follows the tag.
views/programming-languages.ejs
Let's try writing a template that takes advantage of a couple of these syntaxes!
<%
// This is a block where we can include any necessary logic for this page
const pageName = 'Programming Languages';
const programmingLanguagesArray = [
'JavaScript',
'PHP',
'Java',
'Python',
'Julia',
'C',
'Ruby',
'Go',
'Rust',
];
// Remember: Nothing inbetween regular EJS tags will be sent in the response to the client!
%><!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%= pageName %></title>
</head>
<body>
<h1><%= pageName %></h1>
<p>This is the programming languages listing page. We have assigned and displayed an array of programming langauges.</p>
<h2>List:</h2>
<ul>
<% for(const programmingLanguage of programmingLanguagesArray) { %>
<li>
<%= programmingLanguage // The result of this expression will be printed in the resulting HTML string because the opening tag had an "=" sign! %>
</li>
<% } // End of for loop. %>
</ul>
</body>
</html>
index.js
We won't be able to see this new page in our web browser until we've written some logic (a route) into our application that will render and send it to the client. Let's do that now. Make the following addition to your index.js file:
// Bring Express into our file
const express = require('express');
// Choose a port you'd like to use to access your world wide web site
const PORT = 3333;
// Initialize Express and store the result in 'app'
const app = express();
// Set our view engine to "ejs" so that Express will expect and understand EJS template files
app.set('view engine', 'ejs');
// Listen for traffic on your select port
app.listen(PORT, function() { console.log(
// Print a message to terminal as a reminder where you can visit your site when the app is running
`Express application is now available at: http://localhost:${PORT}`
) });
// Create a route; when a client (web browser) visits this page, the function we write will run
app.get('/', function(request, response) {
response.render('index'); // Will run: "views/index.ejs" (we don't have to mention /views or the extension, .ejs)
});
// Let's add another route to make it easy to visit our "Programming Languages" web page! 🆕
app.get('/programming-languages', function(request, response) {
response.render('programming-languages'); // Will run: "views/programming-languages.ejs" (we don't have to mention /views or the extension, .ejs)
});
If you've been following along, remember you'll have to restart your application in-order to see the latest changes. Use CTRL + C in-order to terminate the current Node.js process, and then run the following to start it again:
node index.js
Pass Values to Templates using Template Variables
That works! However, we have placed what would often be considered a bit too much code into the top of our template. You are encouraged to keep data and logic outside of your template where you reasonably can. Let's write a new template that follows a better practice: separation of concerns. This is the idea that there is a time and place for everything. Templates give us the opportunity to keep display-related code in their own place, the rest can live in our main Express application.
In-order to accomplish this we'll add a new route to practice in. This time we'll list Node.js frameworks via a template: views/node-frameworks.ejs.
index.js
Let's start with the route. We'll open index.js again, and add the base logic for this addition. This time our data will live in the route, and our goal will be to pass it to the template we'll create.
For this to work, when we run response.render we'll pass-in two arguments this time:
- Template File Name/Location - We still specify which template file is appropriate for rendering and sending to the client.
- Object Containing Template Variables - These object properties become variables that can be mentioned within our template!
Observe:
// Bring Express into our file
const express = require('express');
// Choose a port you'd like to use to access your world wide web site
const PORT = 3333;
// Initialize Express and store the result in 'app'
const app = express();
// Set our view engine to "ejs" so that Express will expect and understand EJS template files
app.set('view engine', 'ejs');
// Listen for traffic on your select port
app.listen(PORT, function() { console.log(
// Print a message to terminal as a reminder where you can visit your site when the app is running
`Express application is now available at: http://localhost:${PORT}`
) });
// Create a route; when a client (web browser) visits this page, the function we write will run
app.get('/', function(request, response) { // http://localhost:3333/
response.render('index'); // Will run: "views/index.ejs" (we don't have to mention /views or the extension, .ejs)
});
// Let's add another route to make it easy to visit our "Programming Languages" web page!
app.get('/programming-languages', function(request, response) { // http://localhost:3333/programming-languages
response.render('programming-languages'); // Will run: "views/programming-languages.ejs" (we don't have to mention /views or the extension, .ejs)
});
// Let's add yet another route to make it easy to visit a "Node.js Frameworks" web page! 🆕
app.get('/node-frameworks', function(request, response) { // http://localhost:3333/node-frameworks
const pageName = 'Node.js Frameworks';
const nodeFrameworks = [
'Express.js',
'Koa.js',
'Sails.js',
'Next.js',
'Meteor.js',
'Nest.js'
];
// This time we want to pass-in two arguments to response.render:
// 1) The name of the template file we'd like to render
// 2) An object filled with variables—these will become available in our template!
response.render('node-frameworks', { pageName, nodeFrameworks });
});
This won't work just yet, as the views/node-frameworks.ejs file doesn't exist just yet. Let's fix that!
views/node-frameworks.ejs
This time we won't include variable assignments in our template. We'll simply reference the variables by name, knowing that our route has defined and given to us the expected variables.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%= pageName %></title>
</head>
<body>
<h1><%= pageName %></h1>
<p>This is the Node.js frameworks listing page. The array was assigned in the Express application route, and delivered to the template via a "template variables" object.</p>
<h2>List:</h2>
<ul>
<% for(const nodeFramework of nodeFrameworksArray) { %>
<li>
<%= nodeFramework // The result of this expression will be printed in the resulting HTML string because the opening tag had an "=" sign! %>
</li>
<% } // End of for loop. %>
</ul>
</body>
</html>
If everything appears to be in-order, use CTRL + C in your terminal to shut your web application down. Run it again with:
node index.js
To see this new page in the browser, visit: http://localhost:3333/node-frameworks.
Break Down Templates into Smaller, Re-usable "Partials"
At this point, there is another poor pattern that is emerging. We're repeating ourselves a lot in our templates so-far. The <!DOCTYPE html> and <head> are largely the same. It would be nice if we could break this into a smaller, re-usable, mini-template. These smaller template "chunks" that can be re-used are referred to as partials.
You can place partials where you'd like the in the views directory, but it is recommended you stay organized by creating a sub-directory called partials.
In anticipation of using these re-usable partials, let's start building out some!
views/partials/head.ejs
We'll create one that can be the re-usable "top" for each new page:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%= pageName %></title>
</head>
<body>
<h1><%= pageName %></h1>
Notice we've used a template variable, pageName in this partial. That means any page that uses this partial will need us to pass a pageName variable to the template for it to function properly. Keep this in mind as we continue!
views/partials/navigation.ejs
Another piece of the page that is likely to repeat in most projects is a navigation. Let's build one of these as well!
<nav>
<h2>World Wide Web Site Navigation</h2>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/programming-languages">Programming Languages</a></li>
<li><a href="/node-frameworks">Node.js Frameworks</a></li>
<li><a href="/about">About</a></li>
</ul>
</nav>
views/partials/foot.ejs
While we're at it, let's add a footer to make it a bit more complete! We can display the current year, we won't have to worry about going into the code to update it every New Years Day.
<footer>
<h2>World Wide Web Site Footer</h2>
<p>© <%= new Date().getFullYear() // Show current year! %></p>
</footer>
</body>
</html>
Now if we call upon each of these partials in a proper template, we can focus instead on what makes that template, or page, unique.
views/about.ejs
Let's add a new template proper, and make use of our new partials. EJS affords to us a function called includes for bringing in partials, let's make use of it now in a new file views/about.ejs:
<%- include('partials/head.ejs') %>
<%- include('partials/navigation.ejs') %>
<p>Welcome to the about page!</p>
<%- include('partials/foot.ejs') %>
Review the above file. Note how short and succinct the code has become. Again, the focus is now exclusively on what makes this page unique! Also consider that partials like views/partials/navigation.ejs are likely to change and grow over time... with the navigation living within a partial, you can make changes to just one file and all pages that call upon it will be updated!
index.js
We won't be able to see this new "about" page unless there is a route that will actually send the template result to a client. Let's add a new /about route to the index.js Express application file:
// Bring Express into our file
const express = require('express');
// Choose a port you'd like to use to access your world wide web site
const PORT = 3333;
// Initialize Express and store the result in 'app'
const app = express();
// Set our view engine to "ejs" so that Express will expect and understand EJS template files
app.set('view engine', 'ejs');
// Listen for traffic on your select port
app.listen(PORT, function() { console.log(
// Print a message to terminal as a reminder where you can visit your site when the app is running
`Express application is now available at: http://localhost:${PORT}`
) });
// Create a route; when a client (web browser) visits this page, the function we write will run
app.get('/', function(request, response) { // http://localhost:3333/
response.render('index'); // Will run: "views/index.ejs" (we don't have to mention /views or the extension, .ejs)
});
// Let's add another route to make it easy to visit our "Programming Languages" web page!
app.get('/programming-languages', function(request, response) { // http://localhost:3333/programming-languages
response.render('programming-languages'); // Will run: "views/programming-languages.ejs" (we don't have to mention /views or the extension, .ejs)
});
// Let's add yet another route to make it easy to visit a "Node.js Frameworks" web page!
app.get('/node-frameworks', function(request, response) { // http://localhost:3333/node-frameworks
const pageName = 'Node.js Frameworks';
const nodeFrameworks = [
'Express.js',
'Koa.js',
'Sails.js',
'Next.js',
'Meteor.js',
'Nest.js'
];
// This time we want to pass-in two arguments to response.render:
// 1) The name of the template file we'd like to render
// 2) An object filled with variables—these will become available in our template!
response.render('node-frameworks', { pageName, nodeFrameworks });
});
// Let's again add a route to make it easy to visit the "About" web page! 🆕
app.get('/about', function(request, response) {
const pageName = 'About';
response.render('about', { pageName });
});
With the power of EJS, template variables, and now partials we can see that these good practices pay-off. Organization is making it easier to stay focused as the project grows!
In-order to view this latest page in the web browser, recall we'll need to restart the web application. Use CTRL + C to terminate the web application, and restart it via:
node index.js
And there we have it! Visit http://localhost:3333/about to see the new page. This is the pattern you would continue to follow for all new pages as your web application continues to evolve and grow.