Skip to main content
Template engines enable you to use static template files in your application. At runtime, the template engine replaces variables in a template file with actual values and transforms the template into an HTML file sent to the client.

Pug

Clean, whitespace-sensitive syntax

EJS

Embedded JavaScript templates

Handlebars

Minimal logic, mustache compatible

Setting Up a Template Engine

1

Install the template engine

npm install ejs
2

Configure Express

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

const app = express();

// Set views directory
app.set('views', path.join(__dirname, 'views'));

// Set view engine
app.set('view engine', 'ejs');
3

Create template files

Create a file at views/index.ejs:
<!DOCTYPE html>
<html>
  <head>
    <title><%= title %></title>
  </head>
  <body>
    <h1><%= header %></h1>
    <ul>
      <% users.forEach(function(user) { %>
        <li><%= user.name %> - <%= user.email %></li>
      <% }); %>
    </ul>
  </body>
</html>
4

Render the template

app.get('/', function(req, res) {
  res.render('index', {
    title: 'My App',
    header: 'Users',
    users: [
      { name: 'Tobi', email: '[email protected]' },
      { name: 'Loki', email: '[email protected]' }
    ]
  });
});

EJS Templates

EJS (Embedded JavaScript) uses plain JavaScript in templates.

Basic Syntax

<!-- Output escaped value -->
<%= value %>

<!-- Output unescaped value -->
<%- value %>

<!-- JavaScript code -->
<% if (user) { %>
  <h2>Hello <%= user.name %></h2>
<% } %>

<!-- Loop through array -->
<ul>
  <% items.forEach(function(item) { %>
    <li><%= item %></li>
  <% }); %>
</ul>

Custom File Extensions

Register EJS with a custom extension:
const ejs = require('ejs');

// Use .html extension instead of .ejs
app.engine('html', ejs.__express);
app.set('view engine', 'html');

// Now you can use .html files
res.render('users.html', { users: users });
The .html extension makes templates easier to edit in IDEs with HTML syntax highlighting.

Pug Templates

Pug (formerly Jade) uses indentation-based syntax.
1

Install Pug

npm install pug
2

Configure Express

app.set('view engine', 'pug');
3

Create Pug template

Create views/index.pug:
doctype html
html
  head
    title= title
  body
    h1= header
    ul
      each user in users
        li= user.name + ' - ' + user.email

Handlebars Templates

Handlebars provides minimal logic templates.
npm install express-handlebars
const exphbs = require('express-handlebars');

app.engine('handlebars', exphbs.engine());
app.set('view engine', 'handlebars');
<!DOCTYPE html>
<html>
  <head>
    <title>{{title}}</title>
  </head>
  <body>
    <h1>{{header}}</h1>
    <ul>
      {{#each users}}
        <li>{{name}} - {{email}}</li>
      {{/each}}
    </ul>
  </body>
</html>

Custom Template Engines

You can register custom template engines:
const fs = require('fs');
const marked = require('marked');
const escapeHtml = require('escape-html');

// Register .md as a template engine
app.engine('md', function(path, options, fn) {
  fs.readFile(path, 'utf8', function(err, str) {
    if (err) return fn(err);
    
    // Parse markdown and replace variables
    const html = marked.parse(str).replace(/\{([^}]+)\}/g, function(_, name) {
      return escapeHtml(options[name] || '');
    });
    
    fn(null, html);
  });
});

app.set('view engine', 'md');

app.get('/', function(req, res) {
  res.render('index', { title: 'Markdown Example' });
});

Layouts and Partials

<!-- views/header.ejs -->
<header>
  <h1><%= siteName %></h1>
</header>

<!-- views/index.ejs -->
<%- include('header', { siteName: 'My Site' }) %>
<main>
  <p>Content here</p>
</main>
<%- include('footer') %>
//- views/layout.pug
doctype html
html
  head
    title= title
  body
    block content

//- views/index.pug
extends layout

block content
  h1 Welcome
  p This is the index page

Passing Data to Templates

// Pass individual values
res.render('index', {
  title: 'My Page',
  user: req.user
});

// Use res.locals for values available in all templates
app.use((req, res, next) => {
  res.locals.siteName = 'My Website';
  res.locals.currentUser = req.user;
  next();
});
Use res.locals to set variables that should be available in all templates, such as the current user or site configuration.

Caching Templates

Enable view caching in production to improve performance:
if (app.get('env') === 'production') {
  app.enable('view cache');
}

Best Practices

  • Escape user input to prevent XSS attacks
  • Use partials/includes for reusable components
  • Enable caching in production
  • Keep logic minimal in templates
  • Use layouts to avoid duplicating HTML structure
  • Set appropriate content type for non-HTML templates

Build docs developers (and LLMs) love