pug vs ejs vs handlebars vs mustache vs squirrelly
Server-Side Templating Engines for Node.js: Architecture and Selection
pugejshandlebarsmustachesquirrellySimilar Packages:

Server-Side Templating Engines for Node.js: Architecture and Selection

ejs, handlebars, mustache, pug, and squirrelly are JavaScript templating engines used to generate HTML (or other text formats) on the server side. They differ primarily in their syntax philosophy, security models, and feature sets. ejs uses standard JavaScript logic within tags, offering maximum flexibility but requiring manual security hygiene. handlebars and mustache rely on a logic-less approach with custom helpers, prioritizing separation of concerns. pug (formerly Jade) uses a significant whitespace syntax to reduce boilerplate, while squirrelly offers a logic-optional, fast, and secure alternative that works in both Node and the browser without compilation steps.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
pug3,818,59021,85222.5 kB3335 months agoMIT
ejs08,121211 kB283 months agoApache-2.0
handlebars018,6702.81 MB1135 months agoMIT
mustache016,724-1205 years agoMIT
squirrelly0674431 kB252 months agoMIT

Server-Side Templating Engines: Syntax, Security, and Architecture Compared

When building server-rendered applications in Node.js, choosing a templating engine is a foundational architectural decision. The packages ejs, handlebars, mustache, pug, and squirrelly all solve the same problemβ€”merging data with HTMLβ€”but they take vastly different approaches to syntax, logic execution, and security. Let's break down how they handle real-world engineering challenges.

πŸ“ Syntax Philosophy: JavaScript vs. DSL vs. Whitespace

The most immediate difference developers encounter is how the template language feels to write. Some engines let you write raw JavaScript, while others enforce a custom Domain Specific Language (DSL).

ejs embeds standard JavaScript directly inside tags using <% %>.

  • You can write loops, conditionals, and variable declarations just like in a .js file.
  • This reduces context switching for JavaScript developers but couples the view tightly to JS logic.
<!-- ejs: Standard JS logic -->
<ul>
  <% users.forEach(function(user) { %>
    <li><%= user.name %></li>
  <% }); %>
</ul>

handlebars uses a mustache-based syntax {{ }} with custom helpers for logic.

  • It forbids arbitrary JavaScript execution inside the template to enforce separation of concerns.
  • You must register helpers for anything beyond simple variable interpolation.
<!-- handlebars: Helper-based logic -->
<ul>
  {{#each users}}
    <li>{{name}}</li>
  {{/each}}
</ul>

mustache adheres strictly to the logic-less specification.

  • It supports sections ({{#section}}) for loops and conditionals but allows no custom helpers or complex logic.
  • The syntax is identical across implementations in Ruby, Python, and JS.
<!-- mustache: Strictly logic-less -->
<ul>
  {{#users}}
    <li>{{name}}</li>
  {{/users}}
</ul>

pug uses significant whitespace and indentation instead of closing tags.

  • It compiles down to HTML, removing the need for > or </div>.
  • This results in significantly less code but requires strict indentation discipline.
// pug: Whitespace-sensitive syntax
ul
  each user in users
    li= user.name

squirrelly uses a syntax similar to Mustache but allows optional JavaScript logic.

  • It uses {{ }} for variables and {{if}} for logic blocks.
  • Unlike Mustache, it permits function calls and expressions within the template.
<!-- squirrelly: Logic-optional -->
<ul>
  {{for user in users}}
    <li>{{user.name}}</li>
  {{/for}}
</ul>

πŸ”’ Security Model: Auto-Escaping vs. Manual Hygiene

Cross-Site Scripting (XSS) is the primary risk in templating. Engines handle output escaping differently, which directly impacts your security posture.

ejs does not escape output by default when using <%= %>.

  • You must explicitly use <%- %> for raw HTML and remember that <%= %> escapes special characters.
  • If a developer accidentally uses the raw tag with user input, the app becomes vulnerable.
// ejs: Manual attention required
// <%= userInput %> -> Escapes HTML (Safe)
// <%- userInput %> -> Raw HTML (Unsafe if userInput is from user)
const html = ejs.render('<div><%= userInput %></div>', { userInput: '<script>alert(1)</script>' });
// Output: <div>&lt;script&gt;alert(1)&lt;/script&gt;</div>

handlebars escapes all expressions {{ }} by default.

  • To output raw HTML, you must explicitly use triple braces {{{ }}} or the allowProtoMethodsByDefault option carefully.
  • This "secure by default" stance prevents accidental XSS.
// handlebars: Auto-escapes by default
const template = Handlebars.compile('<div>{{input}}</div>');
const html = template({ input: '<script>alert(1)</script>' });
// Output: <div>&lt;script&gt;alert(1)&lt;/script&gt;</div>

mustache also escapes by default.

  • Like Handlebars, it requires explicit syntax to bypass escaping, though the syntax varies slightly by implementation.
  • The strict logic-less nature reduces the surface area for injection attacks.
// mustache: Auto-escapes by default
const html = Mustache.render('<div>{{input}}</div>', { input: '<script>alert(1)</script>' });
// Output: <div>&lt;script&gt;alert(1)&lt;/script&gt;</div>

pug escapes interpolated values by default.

  • Using = escapes content, while != renders raw HTML.
  • The compiler handles the sanitization during the build step.
// pug: Auto-escapes interpolations
const fn = pug.compile('div= userInput');
const html = fn({ userInput: '<script>alert(1)</script>' });
// Output: <div>&lt;script&gt;alert(1)&lt;/script&gt;</div>

squirrelly auto-escapes all variables by default.

  • It provides a specific syntax {{raw}} if you intentionally need unescaped HTML.
  • This matches the security model of modern frameworks like React.
// squirrelly: Auto-escapes by default
const html = Sqrl.render('<div>{{input}}</div>', { input: '<script>alert(1)</script>' });
// Output: <div>&lt;script&gt;alert(1)&lt;/script&gt;</div>

βš™οΈ Extensibility: Helpers vs. Raw Functions

When templates need to format dates, currency, or perform complex checks, the engine must provide a way to extend functionality.

ejs relies on native JavaScript functions passed in the scope.

  • You don't need to register helpers; you just pass utility functions as data.
  • This offers maximum power but can clutter the template namespace.
// ejs: Pass functions directly
ejs.render('<%= formatDate(date) %>', {
  date: new Date(),
  formatDate: (d) => d.toISOString()
});

handlebars requires registering named helpers.

  • Helpers are isolated from the data scope, keeping the template clean.
  • You can create block helpers to manipulate context.
// handlebars: Register helpers
Handlebars.registerHelper('formatDate', (date) => date.toISOString());
const template = Handlebars.compile('{{formatDate date}}');

mustache allows passing functions in the data object but lacks a formal helper registry.

  • Complex logic often has to be pre-processed in the controller before passing to the template.
  • This enforces the "fat controller, thin view" pattern.
// mustache: Functions in data view
Mustache.render('{{formattedDate}}', {
  date: new Date(),
  formattedDate: function() { return this.date.toISOString(); }
});

pug supports mixins and includes for reusable components.

  • You can define blocks of markup and logic that act like functions.
  • It also allows passing functions in the locals object similar to EJS.
// pug: Mixins for reusability
mixin dateFormat(date)
  li= date.toISOString()

ul
  +dateFormat(new Date())

squirrelly supports filters and plugins.

  • You can define custom filters to transform data within the template syntax.
  • It balances the ease of EJS functions with the structure of Handlebars helpers.
// squirrelly: Custom filters
Sqrl.defineFilter('upper', (str) => str.toUpperCase());
Sqrl.render('{{name | upper}}', { name: 'alice' });

πŸš€ Runtime vs. Compile Time Performance

How the template is processed affects server latency and deployment strategies.

ejs compiles templates into JavaScript functions at runtime (or pre-compile time).

  • The first request might be slower if not pre-compiled, but subsequent renders are fast.
  • It relies on the new Function() constructor internally.
// ejs: Compiles to JS function
const fn = ejs.compile('<h1><%= title %></h1>');
// fn is a reusable JavaScript function

handlebars compiles templates into spec-compliant JavaScript.

  • It is generally slower than EJS for complex logic due to the helper lookup overhead.
  • Pre-compilation is recommended for production to reduce payload size.
// handlebars: Compile step
const template = Handlebars.compile('<h1>{{title}}</h1>');

mustache interprets the template string on every render unless pre-compiled.

  • It is typically the slowest of the group for large datasets because it parses the syntax tree repeatedly.
  • Its simplicity makes it easy to port but less optimized for high-throughput Node servers.
// mustache: Interpretation
Mustache.render('<h1>{{title}}</h1>', { title: 'Hello' });

pug is strictly a compile-time engine.

  • Templates must be compiled into JavaScript functions before rendering.
  • You cannot pass a raw string to pug.render in production without a compilation step.
  • This yields very fast runtime performance as the output is pure JS.
// pug: Must be compiled
const fn = pug.compile('h1= title');
// Renders extremely fast as optimized JS

squirrelly compiles templates to JavaScript functions similar to EJS.

  • It is designed to be lightweight and fast, often outperforming Handlebars in benchmarks.
  • It supports both server-side compilation and client-side rendering without heavy dependencies.
// squirrelly: Compiles to JS
const tmpl = Sqrl.compile('<h1>{{title}}</h1>');

🌐 Environment Compatibility: Server vs. Browser

Some teams need to render templates on the client side (e.g., for email previews or static site generation in the browser).

ejs can run in the browser but requires bundling the whole engine.

  • It is heavy for client-side use and rarely chosen for this purpose today.
  • Primarily designed for Node.js server environments.
// ejs: Browser usage requires bundling
// <script src="ejs.js"></script>
// ejs.render(template, data);

handlebars has a dedicated runtime for the browser.

  • You can pre-compile templates on the server and ship only the small runtime to the client.
  • Very common for client-side templating in legacy architectures.
// handlebars: Browser runtime
// Load handlebars.runtime.js
// Execute pre-compiled template function

mustache is fully language-agnostic and has tiny browser implementations.

  • It is an excellent choice for rendering templates directly in the browser without a build step.
  • Minimal footprint makes it ideal for lightweight widgets.
// mustache: Tiny browser footprint
Mustache.render(template, view);

pug is primarily a server-side build tool.

  • While a client-side version exists, it is uncommon because the compilation step is mandatory.
  • Most workflows compile Pug to HTML during the build process, not at runtime in the browser.
// pug: Typically build-time only
// Compiled to HTML via Webpack/Gulp plugin

squirrelly is designed for isomorphic usage out of the box.

  • The same package works in Node and the browser with no changes.
  • It does not require a separate "runtime" vs "compiler" split, simplifying the stack.
// squirrelly: Isomorphic
// Works identically in Node and Browser
Sqrl.render(template, data);

🀝 Similarities: Shared Ground

Despite their differences, these libraries share core objectives and capabilities.

1. πŸ“„ Text Generation

  • All five are capable of generating any text format, not just HTML (e.g., XML, SQL, Emails).
  • They all support variable interpolation and basic output rendering.
// Common usage across all: Variable insertion
// EJS: <%= name %>
// Handlebars: {{name}}
// Mustache: {{name}}
// Pug: = name
// Squirrelly: {{name}}

2. πŸ” Iteration and Conditionals

  • Every engine provides a mechanism to loop over arrays and check boolean conditions.
  • The syntax differs, but the logical capability is universal.
// Conceptual equivalence
// EJS: <% if (active) { %> ... <% } %>
// Handlebars: {{#if active}} ... {{/if}}
// Mustache: {{#active}} ... {{/active}}
// Pug: if active
// Squirrelly: {{if active}} ... {{/if}}

3. 🧩 Component Reuse

  • All support some form of partials or includes to break templates into smaller files.
  • This is essential for maintaining headers, footers, and navigation bars.
// Common pattern: Including a partial
// EJS: <%- include('header') %>
// Handlebars: {{> header}}
// Mustache: {{> header}}
// Pug: include header.pug
// Squirrelly: {{include file="header"}}

πŸ“Š Summary: Key Differences

Featureejshandlebarsmustachepugsquirrelly
Syntax StyleEmbedded JSLogic-less + HelpersStrict Logic-lessWhitespace/IndentationLogic-optional + Filters
SecurityManual (Escape needed)Auto-escapedAuto-escapedAuto-escapedAuto-escaped
Logic PowerHigh (Full JS)Medium (Helpers)Low (None)High (JS + Mixins)Medium (Expressions)
CompilationRuntime or PreRuntime or PreInterpret or PreCompile-time OnlyRuntime or Pre
Browser ReadyHeavyYes (Runtime)Yes (Tiny)Rare (Build step)Yes (Isomorphic)
Learning CurveLow (If you know JS)MediumLowHigh (New syntax)Low

πŸ’‘ The Big Picture

ejs is the pragmatic choice for teams who want to leverage existing JavaScript knowledge without learning a new syntax. It is powerful but demands discipline regarding security. Use it for internal tools or when rapid prototyping with full JS access is needed.

handlebars remains the industry standard for balanced templating. Its helper system provides a clean extension point without compromising the logic-less philosophy. It is the safest bet for large-scale applications where maintainability and separation of concerns are critical.

mustache is the specialist for portability. If your templates need to run in Ruby, Python, and JavaScript without modification, or if you need a tiny browser library, this is the only correct choice. Avoid it for complex app logic.

pug is the productivity booster for developers who hate HTML boilerplate. Its concise syntax speeds up writing views significantly. However, its unique indentation-based structure can be a barrier for collaboration with non-coders. Best for projects where the dev team owns the entire view layer.

squirrelly is the modern contender offering the best of both worlds: the speed and safety of auto-escaping with the flexibility of optional logic. Its isomorphic nature makes it uniquely suited for modern stacks that share code between server and client. Consider it for new projects aiming for a lightweight, secure, and flexible architecture.

How to Choose: pug vs ejs vs handlebars vs mustache vs squirrelly

  • pug:

    Choose pug if your priority is developer velocity and reducing HTML verbosity. Its whitespace-sensitive syntax eliminates closing tags and quotes, making it excellent for teams that value concise code. Avoid it if your designers or content editors need to read raw template files, as the indentation-based structure can be difficult for non-developers to parse.

  • ejs:

    Choose ejs if your team is already proficient in JavaScript and you need to embed complex logic directly into templates without learning a new DSL. It is ideal for legacy projects or scenarios where you need full access to JavaScript functions, but be prepared to manually sanitize outputs to prevent XSS vulnerabilities.

  • handlebars:

    Choose handlebars if you want a balance between structure and extensibility. It enforces a logic-less view layer by default but allows you to register custom helpers for complex behavior. It is the standard choice for projects requiring a robust ecosystem of pre-built helpers and strong separation between data and presentation.

  • mustache:

    Choose mustache if you need a strictly logic-less template engine that guarantees portability across different languages (polyglot environments). It is best for simple rendering tasks where the template must remain purely presentational, and you do not need custom helpers or block iterators beyond the spec.

  • squirrelly:

    Choose squirrelly if you need a modern, secure engine that works seamlessly in both Node.js and the browser without a build step. It is suitable for projects that want logic capabilities similar to EJS but with auto-escaping enabled by default and a smaller footprint, making it a strong candidate for isomorphic applications.

README for pug

Pug

Full documentation is at pugjs.org

Pug is a high performance template engine heavily influenced by Haml and implemented with JavaScript for Node.js and browsers. For bug reports, feature requests and questions, open an issue. For discussion join the chat room.

You can test drive Pug online here.

Build Status Coverage Status Dependency Status DevDependencies Status NPM version Join Gitter Chat

Rename from "Jade"

This project was formerly known as "Jade." However, it has been revealed to us that "Jade" is a registered trademark, and as a result a rename is needed. After some discussion among the maintainers, "Pug" has been chosen as the new name for this project. The next major version will carry "pug" as the package name.

If your package or app currently uses jade, don't worry: we have secured permissions to continue to occupy that package name, although all new versions will be released under pug.

Before the renaming, we had already begun working on an incompatible Jade 2.0.0. We have then made it so that this new major version bump will coincide with the rename to Pug. Therefore, upgrading from Jade to Pug will be the same process as upgrading any other package with a major version bump. Currently, Pug 2.0.0 is still under beta stage, and there are several syntactic differences we have deprecated and removed. Such differences are documented at #2305.

The website and documentation for Pug are still being updated, but if you are new to Pug, you should get started with the new syntax and install the Pug package on npm.

Installation

Package

via npm:

$ npm install pug

Command Line

After installing the latest version of Node.js, install with:

$ npm install pug-cli -g

and run with

$ pug --help

Syntax

Pug is a clean, whitespace sensitive syntax for writing html. Here is a simple example:

doctype html
html(lang="en")
  head
    title= pageTitle
    script(type='text/javascript').
      if (foo) bar(1 + 5)
  body
    h1 Pug - node template engine
    #container.col
      if youAreUsingPug
        p You are amazing
      else
        p Get on it!
      p.
        Pug is a terse and simple templating language with a
        strong focus on performance and powerful features.

becomes

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Pug</title>
    <script type="text/javascript">
      if (foo) bar(1 + 5)
    </script>
  </head>
  <body>
    <h1>Pug - node template engine</h1>
    <div id="container" class="col">
      <p>You are amazing</p>
      <p>Pug is a terse and simple templating language with a strong focus on performance and powerful features.</p>
    </div>
  </body>
</html>

API

For full API, see pugjs.org/api/reference.html

var pug = require('pug');

// compile
var fn = pug.compile('string of pug', options);
var html = fn(locals);

// render
var html = pug.render('string of pug', merge(options, locals));

// renderFile
var html = pug.renderFile('filename.pug', merge(options, locals));

Options

  • filename Used in exceptions, and required when using includes
  • compileDebug When false no debug instrumentation is compiled
  • pretty Add pretty-indentation whitespace to output (false by default)

Browser Support

The latest version of pug can be download for the browser in standalone form from here. It only supports the very latest browsers though, and is a large file. It is recommended that you pre-compile your pug templates to JavaScript.

To compile a template for use on the client using the command line, do:

$ pug --client --no-debug filename.pug

which will produce filename.js containing the compiled template.

Additional Resources

Tutorials:

Implementations in other languages:

Other:

License

MIT