Episode 3 — NodeJS MongoDB Backend Architecture / 3.5 — Template Engine EJS

3.5.a — What is a Template Engine?

In one sentence: A template engine merges an HTML skeleton with dynamic data on the server, producing a finished HTML page that the browser can render immediately — this is server-side rendering (SSR), and it is the foundation of how the web worked before single-page app frameworks existed.

Navigation: <- 3.5 Overview | 3.5.b — Setting Up EJS ->


1. The Core Problem Template Engines Solve

Without a template engine, generating dynamic HTML in Express looks like this:

app.get('/profile', (req, res) => {
  const user = { name: 'Arjun', role: 'Admin' };

  // Manually concatenating HTML — painful and error-prone
  res.send(`
    <html>
      <body>
        <h1>Welcome, ${user.name}</h1>
        <p>Role: ${user.role}</p>
      </body>
    </html>
  `);
});

Problems with this approach:

  • HTML mixed inside JavaScript files — hard to read, harder to maintain
  • No syntax highlighting for the HTML portion
  • No reusable components (you copy-paste headers and footers everywhere)
  • Easy to introduce XSS vulnerabilities (no automatic escaping)
  • Designers cannot edit templates without touching JS logic

A template engine separates the HTML structure into its own file, lets you inject data with special tags, and handles escaping automatically.


2. What Template Engines Actually Do

A template engine performs three steps every time a page is requested:

StepWhat Happens
1. ReadLoad the .ejs (or .hbs, .pug) template file from disk
2. CompileParse the special tags and merge them with the data object you pass
3. OutputProduce a plain HTML string and send it to the browser via res.send()

The browser receives normal HTML — it has no idea a template engine was involved.

   Data Object               Template File              Final HTML
  { name: 'Arjun' }   +   <h1><%= name %></h1>   =   <h1>Arjun</h1>

3. Server-Side Rendering (SSR) Explained

SSR means the server builds the complete HTML before sending it to the client.

Browser requests /dashboard
        |
        v
  Express route handler
        |
        v
  Fetch data (DB, API, etc.)
        |
        v
  Template engine merges data + template
        |
        v
  Complete HTML sent to browser
        |
        v
  Browser paints immediately — no JS needed to show content

Benefits of SSR:

  1. Faster first paint — browser shows content without waiting for JS bundles
  2. SEO friendly — search engine crawlers see real HTML, not an empty <div id="root">
  3. Works without JavaScript — content visible even if JS is disabled or slow to load
  4. Simpler architecture — no separate frontend build step for many projects

Trade-offs:

  • Every page navigation triggers a full server round-trip
  • Less interactive than SPAs without additional client-side JS
  • Server does more work per request (compiling templates)

4. Why Template Engines Exist — The Four Pillars

PillarExplanation
Dynamic ContentShow different data per user, per request — product pages, dashboards, profiles
Reusable LayoutsDefine header, footer, navbar once; include them across every page
Data BindingPass a JavaScript object from the route; the template renders it as HTML
Separation of ConcernsLogic stays in routes/controllers; presentation stays in template files

5. Popular Template Engine Options

EngineSyntax StyleFile ExtensionExample
EJSHTML + <%= %> tags.ejs<h1><%= title %></h1>
Pug (Jade)Indentation-based, no closing tags.pugh1= title
HandlebarsHTML + {{ }} mustache syntax.hbs<h1>{{title}}</h1>
NunjucksJinja2-inspired {% %} blocks.njk<h1>{{ title }}</h1>
MustacheLogic-less {{ }}.mustache<h1>{{title}}</h1>

6. Detailed Comparison

FeatureEJSPugHandlebarsNunjucks
Learning curveLow (just HTML)Medium (new syntax)LowMedium
Full JS in templatesYes — any JS expressionYesNo (logic-less)Limited
Looks like HTMLYesNo (indentation)YesYes
Partials / Includes<%- include() %>include keyword{{> partial}}{% include %}
Layout inheritanceVia packageBuilt-in extendsVia packageBuilt-in extends
npm weekly downloads~15M+~8M+~10M+~2M+
Best forBeginners, quick SSRClean minimal syntaxLogic-less templatesPython devs (Jinja2)

Why we use EJS in this course: It has the lowest barrier — if you know HTML and JavaScript, you already know 90% of EJS. There is no new syntax to memorize.


7. Template Engines vs SPA Frameworks

AspectTemplate Engine (EJS, Pug)SPA Framework (React, Vue, Angular)
RenderingServer builds HTMLBrowser builds HTML from JS
First loadFast (HTML ready)Slower (download JS, then render)
InteractivityPage reload per actionInstant UI updates, no reload
SEOExcellent by defaultRequires SSR/SSG setup (Next.js, Nuxt)
ComplexitySimpleHigher (state mgmt, routing, build tools)
Use caseBlogs, dashboards, admin panels, MVPsComplex interactive apps, SPAs
Data flowServer -> template -> browserAPI -> frontend state -> virtual DOM

When to choose a template engine:

  • Internal tools, admin dashboards, content sites
  • Projects where SEO matters and you want simplicity
  • MVPs and prototypes that need to ship fast
  • Server-rendered forms (login, registration, checkout)

When to choose an SPA framework:

  • Highly interactive UIs (real-time chat, drag-and-drop)
  • Apps where the user stays on one page for a long session
  • Teams with dedicated frontend engineers and build tooling

8. Template Engines in the MVC Pattern

Template engines are the View layer in Model-View-Controller:

┌─────────────────────────────────────────────┐
│                   MVC Pattern               │
├──────────┬──────────────┬───────────────────┤
│  Model   │  Controller  │      View         │
│          │              │                   │
│ Database │ Route/Logic  │ Template Engine   │
│ schemas, │ fetches data,│ (EJS files)       │
│ queries  │ calls model, │ receives data,    │
│          │ passes data  │ renders HTML      │
│          │ to view      │                   │
└──────────┴──────────────┴───────────────────┘

Concrete Express example:

// MODEL — data layer (simplified)
function getUser(id) {
  return db.collection('users').findOne({ _id: id });
}

// CONTROLLER — route handler
app.get('/user/:id', async (req, res) => {
  const user = await getUser(req.params.id);   // Model
  res.render('profile', { user });              // View (EJS template)
});
<!-- VIEW — views/profile.ejs -->
<h1><%= user.name %></h1>
<p>Email: <%= user.email %></p>

The controller never writes HTML. The template never queries the database. Each layer has one job.


9. How EJS Fits Into Express Request Flow

1. Browser sends GET /dashboard
2. Express matches route: app.get('/dashboard', handler)
3. Handler fetches data (DB query, API call, etc.)
4. Handler calls: res.render('dashboard', { stats, user })
5. Express asks EJS engine to compile views/dashboard.ejs with the data
6. EJS returns a complete HTML string
7. Express sends that HTML as the response (Content-Type: text/html)
8. Browser receives and paints the page

10. A Mental Model for Beginners

Think of a template engine as mail merge in a word processor:

  • The template is the letter with blank fields: Dear ____,
  • The data is the spreadsheet of names and addresses
  • The engine fills in every blank and prints each personalized letter

EJS does the same thing — but the "letter" is an HTML page and the "spreadsheet" is a JavaScript object.


11. Key Takeaways

  1. A template engine generates HTML by merging a template file with a data object on the server.
  2. This process is called server-side rendering (SSR) — the browser gets finished HTML.
  3. EJS uses HTML syntax with <%= %> tags — no new language to learn.
  4. Template engines are the View in MVC; they never contain business logic or database queries.
  5. Use template engines for content sites, admin panels, MVPs; use SPA frameworks for highly interactive apps.

Explain-It Challenge

Explain without notes:

  1. What is the difference between server-side rendering and client-side rendering? Name one advantage of each.
  2. Why would you pick EJS over Pug for a team of beginners?
  3. Where does the template engine sit in the MVC pattern, and what does it receive from the controller?

Navigation: <- 3.5 Overview | 3.5.b — Setting Up EJS ->