Episode 3 — NodeJS MongoDB Backend Architecture / 3.5 — Template Engine EJS

3.5 — Template Engine EJS: Quick Revision

Episode 3 supplement — print-friendly.

How to use

Skim -> drill weak spots in 3.5.a through 3.5.e -> 3.5-Exercise-Questions.md.


What is a Template Engine?

  • Merges an HTML template file with a data object on the server
  • Produces complete HTML — the browser receives a finished page (SSR)
  • EJS = HTML + <%= %> tags — lowest learning curve of all template engines
  • Template engines are the View in MVC

Setup (3 steps)

npm install ejs
const express = require('express');
const path = require('path');
const app = express();

app.set('view engine', 'ejs');                        // Step 1: set engine
app.set('views', path.join(__dirname, 'views'));       // Step 2: set views path
app.use(express.static(path.join(__dirname, 'public')));  // Static files
// Step 3: render in routes
app.get('/', (req, res) => {
  res.render('home', { title: 'Home', user: req.user });
});

Five EJS Tag Types

TagNamePurposeOutput?
<%= expr %>EscapedPrint value, HTML-safe (XSS protected)Yes
<%- expr %>UnescapedPrint raw HTML (for includes, trusted content)Yes
<% code %>ScriptletRun JS (if, for, variables) — no outputNo
<%# text %>CommentStripped from output (invisible to browser)No
<%- include() %>IncludeInsert another EJS fileYes

Escaped vs Unescaped — The Critical Difference

<%= "<script>alert('xss')</script>" %>
<!-- Output: &lt;script&gt;alert(&#39;xss&#39;)&lt;/script&gt; — SAFE -->

<%- "<script>alert('xss')</script>" %>
<!-- Output: <script>alert('xss')</script> — DANGEROUS with user input -->

Rule: Always <%= %> for user data. Only <%- %> for includes and trusted HTML.


Control Flow

if / else

<% if (user.isAdmin) { %>
  <span>Admin</span>
<% } else { %>
  <span>User</span>
<% } %>

forEach

<% items.forEach(function(item) { %>
  <li><%= item.name %></li>
<% }); %>

for loop

<% for (let i = 0; i < items.length; i++) { %>
  <li><%= items[i].name %></li>
<% } %>

The locals Pattern

<%# WRONG — crashes if 'error' was not passed %>
<% if (error) { %>

<%# CORRECT — safely returns undefined %>
<% if (locals.error) { %>
  <div class="alert"><%= error %></div>
<% } %>

locals is the data object from res.render(). Property lookup on an object returns undefined. Variable lookup in scope throws ReferenceError.


Partials

<%- include('partials/header') %>
<%- include('partials/card', { item: product, showPrice: true }) %>
PatternWhat it does
include('partials/header')Insert header partial
include('partial', { data })Pass extra data to partial
Partials inside partialsAllowed (header includes navbar)

Layout Pattern

Manual (header + footer includes)

<%- include('partials/header') %>
  <h1>Page Content</h1>
<%- include('partials/footer') %>

With express-ejs-layouts

npm install express-ejs-layouts
app.use(require('express-ejs-layouts'));
app.set('layout', 'layouts/main');

Layout file uses <%- body %> — page content injected there.


Static Files

app.use(express.static(path.join(__dirname, 'public')));
File on DiskURL in Template
public/css/style.css/css/style.css
public/js/main.js/js/main.js
public/img/logo.png/img/logo.png

Always use absolute paths (start with /). Relative paths break on nested URLs.


Directory Structure

project/
├── app.js
├── public/
│   ├── css/style.css
│   ├── js/main.js
│   └── img/
├── views/
│   ├── partials/
│   │   ├── header.ejs
│   │   ├── navbar.ejs
│   │   └── footer.ejs
│   ├── home.ejs
│   ├── about.ejs
│   └── 404.ejs
└── package.json

Common Errors

ErrorCauseFix
Cannot find module 'ejs'Not installednpm install ejs
No default engine was specifiedMissing app.set('view engine', 'ejs')Add the setting
Failed to lookup viewFile not in views/ or wrong nameCheck path and spelling
ReferenceError: x is not definedVariable not passed to templateUse locals.x
CSS/JS 404Missing express.static or wrong pathAdd middleware, use absolute paths

Template Engines Comparison (One-Liner)

EngineSyntaxBest For
EJSHTML + <%= %>Beginners, familiar HTML
PugIndentation, no tagsConcise, layout inheritance
HandlebarsHTML + {{ }}Logic-less, strict separation
Nunjucks{% %} blocksJinja2/Python devs

SSR vs CSR

SSR (Template Engine)CSR (SPA Framework)
HTML builtServerBrowser
First paintFastSlower (JS download)
SEOExcellentRequires extra setup
InteractivityPage reloadInstant updates
Use caseBlogs, admin, MVPComplex interactive apps

One-Liners

  • Template engine = data + HTML skeleton = finished page.
  • <%= %> = safe output. <%- %> = raw output. <% %> = logic only.
  • locals = the safe way to check optional template variables.
  • Partials = DRY for templates. Write once, include everywhere.
  • express.static = serve CSS/JS/images. Absolute paths only.

End of 3.5 quick revision.