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
| Tag | Name | Purpose | Output? |
|---|---|---|---|
<%= expr %> | Escaped | Print value, HTML-safe (XSS protected) | Yes |
<%- expr %> | Unescaped | Print raw HTML (for includes, trusted content) | Yes |
<% code %> | Scriptlet | Run JS (if, for, variables) — no output | No |
<%# text %> | Comment | Stripped from output (invisible to browser) | No |
<%- include() %> | Include | Insert another EJS file | Yes |
Escaped vs Unescaped — The Critical Difference
<%= "<script>alert('xss')</script>" %>
<!-- Output: <script>alert('xss')</script> — 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 }) %>
| Pattern | What it does |
|---|---|
include('partials/header') | Insert header partial |
include('partial', { data }) | Pass extra data to partial |
| Partials inside partials | Allowed (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 Disk | URL 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
| Error | Cause | Fix |
|---|---|---|
Cannot find module 'ejs' | Not installed | npm install ejs |
No default engine was specified | Missing app.set('view engine', 'ejs') | Add the setting |
Failed to lookup view | File not in views/ or wrong name | Check path and spelling |
ReferenceError: x is not defined | Variable not passed to template | Use locals.x |
| CSS/JS 404 | Missing express.static or wrong path | Add middleware, use absolute paths |
Template Engines Comparison (One-Liner)
| Engine | Syntax | Best For |
|---|---|---|
| EJS | HTML + <%= %> | Beginners, familiar HTML |
| Pug | Indentation, no tags | Concise, layout inheritance |
| Handlebars | HTML + {{ }} | Logic-less, strict separation |
| Nunjucks | {% %} blocks | Jinja2/Python devs |
SSR vs CSR
| SSR (Template Engine) | CSR (SPA Framework) | |
|---|---|---|
| HTML built | Server | Browser |
| First paint | Fast | Slower (JS download) |
| SEO | Excellent | Requires extra setup |
| Interactivity | Page reload | Instant updates |
| Use case | Blogs, admin, MVP | Complex 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.