CodingNic

Server-Rendered Views

EJS Templating Syntax

Server-Rendered Views 12 min read

EJS Templating Syntax

Objectives

By the end of this lesson, you should be able to:

  • Use <% %> to run JavaScript logic inside a template, without printing output
  • Loop over an array with .forEach() inside a template
  • Use an if/else inside a template to conditionally render content

💡 Why this matters: A real page rarely shows one fixed value, it shows a list of products, a conditional “in stock” badge, an empty state when there’s nothing to show. EJS’s control-flow tags handle exactly this, using plain JavaScript, no separate templating language to learn.

⚠️ A note on verification: every snippet and every response shown below was actually run and tested with real HTTP requests.

Scriptlet Tags: <% %>

EJS has two tag types: <%= expression %> (Lesson 1) evaluates expression and prints the result, <% statement %> runs statement as plain JavaScript but prints nothing, used for loops, conditionals, and any logic that doesn’t itself produce a value to display.

Looping Over an Array

html
<!-- views/products.ejs -->
<h1>Products</h1>
<% if (products.length === 0) { %>
  <p>No products found.</p>
<% } else { %>
  <ul>
    <% products.forEach(function(product) { %>
      <li>
        <%= product.name %> - $<%= product.price %>
        <% if (product.inStock) { %>
          <strong>(in stock)</strong>
        <% } else { %>
          (out of stock)
        <% } %>
      </li>
    <% }); %>
  </ul>
<% } %>
javascript
const products = [
  { name: 'Keyboard', price: 45, inStock: true },
  { name: 'Monitor', price: 210, inStock: false }
];

app.get('/products', (req, res) => {
  res.render('products', { products });
});
bash
curl http://localhost:4102/products
text
<h1>Products</h1>
<ul>
  <li>Keyboard - $45 <strong>(in stock)</strong></li>
  <li>Monitor - $210 (out of stock)</li>
</ul>

This is a genuine JavaScript if/else and .forEach() (Module 2’s array methods), split across <% %> tags with regular HTML in between, EJS templates are, quite literally, JavaScript with an HTML-templating syntax layered on top, nothing new to learn beyond the tag syntax itself.

Empty States

bash
curl http://localhost:4102/products-empty
text
<h1>Products</h1>

  <p>No products found.</p>

Passing an empty array renders the if (products.length === 0) branch instead, exactly like a normal JavaScript conditional, this is the standard pattern for handling “no results” states in a real application.

Escaped vs Unescaped Output

html
<p>Escaped: <%= userInput %></p>
<p>Unescaped: <%- userInput %></p>
javascript
res.render('escaping', { userInput: '<strong>bold</strong>' });
text
<p>Escaped: &lt;strong&gt;bold&lt;/strong&gt;</p>
<p>Unescaped: <strong>bold</strong></p>

<%= %> HTML-escapes its output automatically, < becomes &lt;, and so on, this is a critical security default, it prevents a value containing HTML or a <script> tag from being interpreted as real markup (this class of vulnerability is called Cross-Site Scripting, or XSS). <%- %> outputs raw, unescaped HTML instead, only appropriate for content you trust completely, like the partials in the next lesson, never for raw user input.

Try It

  1. Render a template looping over an array of at least three names, printing each in an <li>.
  2. Add an if/else inside the loop that shows different text depending on a boolean property of each item (like inStock above).
  3. Pass an empty array to the same template, and confirm an appropriate “nothing here” message renders instead of an empty list.
  4. Render a value containing <em> tags with both <%= %> and <%- %>, and explain, in your own words, why the escaped version is the safer default for anything coming from user input.

Recap

  • <% statement %> runs JavaScript without printing, used for loops and conditionals, <%= expression %> prints an escaped value.
  • .forEach() and if/else inside <% %> tags work exactly like normal JavaScript, split across HTML.
  • <%= %> escapes HTML automatically (safe default, prevents XSS), <%- %> outputs raw HTML, only for trusted content.

Next lesson: passing more complex data into a view.