AV
HomeAboutProjectBlog

© 2026 Ave syah Shina. All rights reserved.

  1. Home
  2. Blog
  3. 13 — Template Engines — Generating HTML on the Server

13 — Template Engines — Generating HTML on the Server

August 13, 20265 min read
Download as Markdown

"Just interpolate the variables into an HTML string" was my template-engine model, and it hid the actual shape. The model that finally stuck is precise: a template engine is a function from data to an HTML string. I give it a template file plus a data object; it returns the finished HTML, which I send down to the browser [1]. The engines differ mainly in their syntax philosophy, and the choice is almost entirely about which philosophy my team finds readable.

The framing that landed for me is "data in, HTML out," then the three syntax philosophies worth knowing.

Why a template engine at all

The naive approach — concatenating strings — fails the moment the markup is non-trivial. Escaping user input to prevent XSS (cross-site scripting, where a user sneaks their own code into the page), looping over arrays to build table rows, conditionally rendering sections, and reusing layout pieces (header, footer) are all things I need on every page. A template engine gives me a small language for these operations, baked into the template file itself, with automatic HTML escaping for safety.

This is the layer server-side rendering (SSR) lived at before React and its peers took over the "component" job. For a traditional server-rendered app — Express rendering an EJS view per request, say — a template engine is still the standard tool. For an app whose UI is a React/Vue SPA, the template engine is mostly absent; the framework is the templating layer.

EJS: JavaScript in HTML

EJS (Embedded JavaScript) is the most HTML-literal of the three. The template is regular HTML with <% ... %> tags containing JavaScript — <%= %> outputs an escaped value, <% %> runs arbitrary JS (loops, conditionals), and <%- %> outputs unescaped (for trusted HTML) [2][3].

<% if (user) { %>
<h1>Hello, <%= user.name %></h1>
<ul>
<% user.posts.forEach(post => { %>
<li><%= post.title %></li>
<% }) %>
</ul>
<% } %>

The appeal is familiarity — if I know HTML and JavaScript, I know EJS. There is no new syntax to learn; the templating is just JavaScript inside the tags. The cost is verbosity — the loop above is more characters than the equivalent in Pug — and the tags can get noisy in larger templates. EJS is the safe, boring default for a server-rendered app, and "boring" is a virtue when the goal is to ship pages quickly.

Pug: terse and indentation-based

Pug (formerly Jade) takes the opposite philosophical stance — eliminate the angle brackets and closing tags entirely [4]. HTML structure is expressed by indentation, classes and ids use CSS-like shorthand, and the template compiles to HTML at render time.

if user
h1 Hello, #{user.name}
ul
each post in user.posts
li= post.title

That is the same output as the EJS example, in roughly half the characters. The appeal is brevity and readability once the syntax clicks — there is no closing-tag noise, and the structure mirrors the indentation I already use for code. The cost is a learning curve (a new syntax, not just JavaScript-in-HTML) and the loss of HTML's "anything between < and > is a tag" idea, which makes hand-debugging the raw template harder. Pug suits teams that value terseness and are willing to invest in the syntax.

Marko: compile-to-components

Marko is a different beast — it is HTML-based like EJS, but the templates compile to JavaScript modules and support custom tags, async rendering, and streaming [5]. In spirit, Marko is closer to a component framework (React, Vue) than to a classic template engine; it was built to compile and optimize, not just to render a string.

<h1>Hello, ${user.name}</h1>
<ul>
<for|post| of=user.posts>
<li>${post.title}</li>
</for>
</ul>

The ${} syntax interpolates values; the <for|post| of=...> is a custom tag that compiles to an optimized loop. Marko's pitch is performance — compiled templates, streaming responses, and a component model that scales — but adopting it means buying into its component philosophy, which is a larger commitment than swapping EJS for Pug.

Choosing between them

The three are points on a syntax-philosophy axis, not a quality ranking:

  • EJS — JavaScript-in-HTML, no new syntax, verbose. The default for a classic server-rendered app where familiarity matters most.
  • Pug — terse indentation language, no closing tags, requires learning the syntax. Suits teams that value brevity.
  • Marko — compiled, component-oriented, performance-focused. A larger commitment; closer to adopting a component framework than a template engine.

The choice between EJS and Pug is largely taste. Marko is a different decision — whether to move from "template that produces a string" to "compiled component tree" — and that depends on whether the app's UI complexity justifies it.

How I use this

The model I keep is "data in, HTML out, syntax philosophy decides the engine." For a traditional server-rendered Express app, I default to EJS, because there is no new syntax to teach and the verbosity is acceptable for the page counts involved. I reach for Pug on projects where the team already knows it and values the brevity. I treat Marko (and component-framework SSR generally) as a separate decision: worth it when the UI is complex enough that components pay for themselves, overkill for a few server-rendered pages. And regardless of engine, I lean on the automatic HTML escaping for any value that came from user input — XSS is the failure mode that template engines exist partly to prevent. The way of thinking — engine as a function from data to HTML — is what keeps the choice grounded.

References

[1] "Getting Started with Pug," pugjs.org. [Online]. Available: https://pugjs.org/api/getting-started.html

[2] "EJS — Embedded JavaScript templates," ejs.co. [Online]. Available: https://ejs.co/#docs

[3] "ejs," npm. [Online]. Available: https://www.npmjs.com/package/ejs

[4] "Pug.js tutorial," zetcode.com. [Online]. Available: https://zetcode.com/javascript/pugjs/

[5] "Marko Documentation," markojs.com. [Online]. Available: https://markojs.com/docs/guides-overview/

Knowledge check · Question 1 of 5

At its core, what does a template engine do?

Comments

Leave a Comment

You must be signed in to comment

0 Comments

No comments yet. Be the first to comment!