Node.js Template Engines with Express

A template engine allows HTML pages to be generated dynamically by embedding JavaScript logic and data directly into HTML files. Instead of sending a hardcoded HTML string from the server, a template engine merges a layout file (the template) with actual data, producing the final HTML that is sent to the browser.

EJS (Embedded JavaScript Templating) is one of the most popular and beginner-friendly template engines for Express. It uses plain HTML with special tags that allow JavaScript expressions, loops, conditions, and variables to be embedded.

Why Use a Template Engine?

Without a template engine, generating dynamic HTML requires manually concatenating strings — which becomes messy and unreadable. With EJS, the HTML structure is kept clean and readable while data is inserted wherever needed using simple tags.

Installing EJS

npm install ejs

Setting Up EJS in Express

const express = require('express');
const path = require('path');
const app = express();

// Set EJS as the template engine
app.set('view engine', 'ejs');

// Set the folder where template files are stored
app.set('views', path.join(__dirname, 'views'));

app.listen(3000, () => console.log('Server running on port 3000'));

By convention, EJS template files are stored in a views/ folder and use the .ejs file extension.

Project Structure

my-express-app/
├── app.js
├── views/
│   ├── index.ejs
│   ├── about.ejs
│   └── user.ejs
└── public/
    └── style.css

Creating the First EJS Template

views/index.ejs

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title><%= title %></title>
</head>
<body>
  <h1><%= heading %></h1>
  <p>Welcome, <%= username %>! You are logged in.</p>
</body>
</html>

Rendering the Template from a Route

// app.js

app.get('/', function(req, res) {
  res.render('index', {
    title: 'My App',
    heading: 'Dashboard',
    username: 'Alice'
  });
});

res.render('index', data) tells Express to find the views/index.ejs file, fill in the placeholders with the provided data, and send the resulting HTML to the browser.

EJS Tags – Quick Reference

TagPurposeExample
<%= %>Output the value (HTML-escaped)<%= username %>
<%- %>Output raw HTML (not escaped)<%- htmlContent %>
<% %>Execute JavaScript (no output)<% if (loggedIn) { %>
<%# %>Comment — not rendered or executed<%# This is a comment %>
<%- include() %>Include another EJS file (partial)<%- include('partials/header') %>

Using Conditionals in EJS

views/greeting.ejs

<!DOCTYPE html>
<html>
<body>
  <% if (isLoggedIn) { %>
    <h1>Welcome back, <%= username %>!</h1>
    <p><a href="/logout">Logout</a></p>
  <% } else { %>
    <h1>Please log in.</h1>
    <p><a href="/login">Login</a></p>
  <% } %>
</body>
</html>
app.get('/greeting', function(req, res) {
  res.render('greeting', {
    isLoggedIn: true,
    username: 'Bob'
  });
});

Looping Through Arrays in EJS

views/products.ejs

<!DOCTYPE html>
<html>
<body>
  <h1>Product List</h1>
  <ul>
    <% products.forEach(function(product) { %>
      <li>
        <strong><%= product.name %></strong> — $<%= product.price %>
      </li>
    <% }); %>
  </ul>
</body>
</html>
app.get('/products', function(req, res) {
  const products = [
    { name: 'Laptop', price: 999 },
    { name: 'Phone', price: 499 },
    { name: 'Headphones', price: 149 }
  ];
  res.render('products', { products });
});

Using Partials – Reusable Template Pieces

Partials are reusable pieces of EJS code, like a header, footer, or navigation bar, that can be included in multiple templates.

views/partials/header.ejs

<header>
  <h2>My Website</h2>
  <nav>
    <a href="/">Home</a> |
    <a href="/about">About</a> |
    <a href="/products">Products</a>
  </nav>
</header>

views/partials/footer.ejs

<footer>
  <p>&copy; 2026 My Website. All rights reserved.</p>
</footer>

views/index.ejs with Partials

<!DOCTYPE html>
<html>
<body>
  <%- include('partials/header') %>

  <main>
    <h1><%= heading %></h1>
    <p><%= description %></p>
  </main>

  <%- include('partials/footer') %>
</body>
</html>

Now every page that includes the header and footer partial shares the same navigation and footer — updating one file updates all pages automatically.

Passing Data to Partials

Data can also be passed directly to included partials:

<%- include('partials/header', { pageTitle: 'Home' }) %>

Inside header.ejs, <%= pageTitle %> would render "Home".

Complete Example – A Blog Post Page

views/post.ejs

<!DOCTYPE html>
<html>
<head>
  <title><%= post.title %></title>
</head>
<body>
  <%- include('partials/header') %>

  <article>
    <h1><%= post.title %></h1>
    <p>By <%= post.author %> on <%= post.date %></p>
    <p><%= post.content %></p>
  </article>

  <h3>Tags</h3>
  <ul>
    <% post.tags.forEach(function(tag) { %>
      <li><%= tag %></li>
    <% }); %>
  </ul>

  <%- include('partials/footer') %>
</body>
</html>
app.get('/post/:id', function(req, res) {
  const post = {
    title: 'Getting Started with Node.js',
    author: 'Alice',
    date: 'March 14, 2026',
    content: 'Node.js is a powerful runtime for server-side JavaScript...',
    tags: ['Node.js', 'JavaScript', 'Backend']
  };
  res.render('post', { post });
});

Key Points

  • EJS is a template engine that allows JavaScript expressions and logic to be embedded inside HTML files.
  • Set EJS as the view engine with app.set('view engine', 'ejs') and store templates in the views/ folder.
  • Use res.render('templateName', data) to render an EJS file and pass data to it.
  • <%= %> outputs an escaped value; <%- %> outputs raw HTML; <% %> runs code without output.
  • Loops and conditionals work inside <% %> tags using standard JavaScript syntax.
  • Partials allow reusable HTML components (headers, footers) to be included across multiple templates.
  • EJS is ideal for server-rendered websites; for API-only backends (like those consumed by React), template engines are not required.

Leave a Comment

Your email address will not be published. Required fields are marked *