Skip to content

Express Template Engines — Complete Guide to EJS, Pug, and Handlebars

DodaTech Updated 2026-06-28 4 min read

In this tutorial, you will learn about Express Template Engines. We cover key concepts, practical examples, and best practices to help you master this topic.

Express template engines render dynamic HTML on the server by combining templates with data from route handlers, enabling server-side rendering of web pages.

What You'll Learn

By the end of this tutorial, you'll set up EJS, Pug, and Handlebars in Express, pass data to templates, use layouts and partials, and choose the right engine for your project.

Why Template Engines Matter

Server-side rendering (SSR) produces fully-rendered HTML on the server, improving SEO and initial page load speed. Template engines make SSR maintainable with reusable components.

Real-World Use

A dashboard application uses EJS to render user-specific data into HTML templates. The sidebar, header, and footer are partials reused across all pages, reducing code duplication.

Express Templates Learning Path

flowchart LR
  A[Error Handling] --> B[Template Engines]
  B --> C[Static Files]
  C --> D[Sessions]
  D --> E[Security]
  A --> F{You Are Here}
  style F fill:#f90,color:#fff

EJS Setup and Syntax

npm install ejs
app.set("view engine", "ejs");
app.set("views", "./views");
// views/index.ejs
// <h1>Welcome, <%= user.name %></h1>
// <% if (user.isAdmin) { %> <p>Admin Panel</p> <% } %>
app.get("/", (req, res) => {
  res.render("index", { user: { name: "Alice", isAdmin: true } });
});

Pug Setup and Syntax

npm install pug
app.set("view engine", "pug");
app.set("views", "./views");
// views/index.pug
// h1= `Welcome, ${user.name}`
// if user.isAdmin
//   p Admin Panel
app.get("/", (req, res) => {
  res.render("index", { user: { name: "Alice", isAdmin: true } });
});

Handlebars Setup

npm install express-handlebars
import { engine } from "express-handlebars";
app.engine("handlebars", engine());
app.set("view engine", "handlebars");
app.set("views", "./views");
// views/index.handlebars
// <h1>Welcome, {{user.name}}</h1>
// {{#if user.isAdmin}}<p>Admin Panel</p>{{/if}}

Layouts and Partials

EJS

<!-- views/layout.ejs -->
<!DOCTYPE html><html><body>
  <%- include('partials/header') %>
  <%- body %>
  <%- include('partials/footer') %>
</body></html>

Pug

//- views/layout.pug
html
  body
    include partials/header
    block content
    include partials/footer

Handlebars

{{! views/layouts/main.handlebars }}
<html><body>
  {{> header}}
  {{{body}}}
  {{> footer}}
</body></html>

Passing Data to Templates

app.get("/users", async (req, res) => {
  const users = await db.users.findAll();
  res.render("users/index", {
    title: "All Users",
    users,
    count: users.length,
    isLoggedIn: true,
    helpers: { formatDate: (d) => d.toLocaleDateString() }
  });
});

Common Mistakes

1. Forgetting to Set views Directory

Express looks for views in the default directory. Always set app.set("views", "./views") explicitly.

2. Using Wrong Syntax for Escaping

EJS: <%= %> escapes HTML, <%- %> renders raw HTML. Using <%= %> for HTML strings escapes the tags.

3. Not Handling Empty Data

When data is undefined, templates throw errors. Always provide defaults or conditional checks.

4. Mixing Multiple Template Engines

Stick to one template engine per project. Mixing engines confuses developers and complicates the build Process.

5. Putting Too Much Logic in Views

Templates are for presentation. Keep business logic in controllers and pass computed values to views.

Practice Questions

1. What is the difference between server-side rendering and client-side rendering?

SSR renders HTML on the server and sends fully-rendered pages. CSR sends JavaScript that renders HTML in the browser.

2. How do you create a layout in EJS?

Use include statements. Create a layout.ejs with <%- include('header') %> and <%- include('footer') %> around the content.

3. Why use partials in templates?

Partials let you reuse HTML snippets (headers, footers, navigation) across multiple pages, reducing duplication.

4. What is the difference between <%= and <%- in EJS?

<%= escapes HTML (safe for user input). <%- outputs raw HTML (use only with trusted content).

5. Challenge: Create an EJS template that renders a list of users with a conditional admin badge.

<!-- views/users.ejs -->
<h1><%= title %></h1>
<ul>
  <% users.forEach(user => { %>
    <li>
      <%= user.name %>
      <% if (user.role === 'admin') { %>
        <span class="badge">Admin</span>
      <% } %>
    </li>
  <% }) %>
</ul>

FAQ

Which template engine should I use?

EJS if you prefer HTML-like syntax. Pug if you want concise indentation-based syntax. Handlebars if you want logic-less templates.

Can I use React/Vue with Express instead of template engines?

Yes, for SPAs. Template engines are for traditional SSR. React/Vue need separate build tooling.

How do I cache rendered templates?

Express caches templates in production by default. Set app.set('view cache', false) in development for live reload.

Can I nest partials inside partials?

Yes, partials can include other partials. Be careful about depth to maintain readability.

How do I access environment variables in templates?

Pass them from the controller: res.render('page', { env: process.env }). Don't access process.env directly in templates.

Mini Project: User Dashboard with EJS

Build a dashboard that renders user data with EJS templates.

// app.js
app.set("view engine", "ejs");
app.set("views", "./views");
app.get("/dashboard", (req, res) => {
  res.render("dashboard", {
    user: { name: "Alice", email: "alice@example.com", role: "admin" },
    stats: { posts: 42, comments: 128, followers: 509 },
    recentPosts: [
      { title: "Getting Started with Express", date: "2026-06-01" },
      { title: "Node.js Best Practices", date: "2026-06-15" }
    ]
  });
});
<!-- views/dashboard.ejs -->
<h1>Welcome, <%= user.name %></h1>
<div class="stats">
  <div>Posts: <%= stats.posts %></div>
  <div>Comments: <%= stats.comments %></div>
  <div>Followers: <%= stats.followers %></div>
</div>
<h2>Recent Posts</h2>
<ul>
  <% recentPosts.forEach(post => { %>
    <li><%= post.title %> (<%= post.date %>)</li>
  <% }) %>
</ul>

What's Next

Express Static Files Express Sessions Express Security

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro