Building Pages with EJS

EJS 6.0.1 689,492 (Apache-2.0) is the smallest step from HTML: a template is an HTML file with JavaScript in tags. <%= %> interpolates and escapes, <%- %> interpolates raw, <% %> runs a statement without output, and include pulls in a partial relative to the current file.

views/books.ejs, after npm install ejs@6.0.1JavaScript
<%- include('partials/head', { title }) %>
<h1><%= title %></h1>
<p>Showing <%= books.length %> books for <%= user.name %>.</p>
<ul>
<% for (const b of books) { %>
  <li><%= b.title %> — <%= b.year %><% if (b.tag) { %> <em><%= b.tag %></em><% } %></li>
<% } %>
</ul>
<p><%= raw %></p>
<p><%- raw %></p>

Rendered with { title: 'Bookshelf', books: [...], user: { name: 'Ada' }, raw: '<b>bold</b>' }, the server returned the page below: the escaped paragraph shows the markup as text, the raw one applies it.

Server response from GET /ejs, blank lines collapsedHTMLLive
<!doctype html><html lang="en">
<head><meta charset="utf-8"><title>Bookshelf</title></head><body>
<h1>Bookshelf</h1>
<p>Showing 2 books for Ada.</p>
<ul><li>Dune — 1965 <em>classic</em></li><li>Neuromancer — 1984</li></ul>
<p>&lt;b&gt;bold&lt;/b&gt;</p>
<p><b>bold</b></p>
</body></html>
Browser output of Listing 3.30
Browser output of 30

That difference is the whole security story of server-rendered HTML. <%= %> escapes &, <, >, " and ', so user text can never open a tag; <%- %> trusts the string completely and belongs only on markup you generated or sanitized yourself (Escaping Output).

EJS has no layout syntax; the usual pattern is two includes, a head and a foot, as partials/head.ejs shows. Variables passed as include's second argument are visible only inside the partial, and a trailing -%> trims the newline after a tag. Loops and conditionals are plain JavaScript — EJS's selling point and its risk: nothing stops you from putting a database call in a template. Keep templates dumb, computing totals, dates and permission flags in the route handler; templates that only interpolate are easy to test and easy to port.