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:
| Step | What Happens |
|---|---|
| 1. Read | Load the .ejs (or .hbs, .pug) template file from disk |
| 2. Compile | Parse the special tags and merge them with the data object you pass |
| 3. Output | Produce 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:
- Faster first paint — browser shows content without waiting for JS bundles
- SEO friendly — search engine crawlers see real HTML, not an empty
<div id="root"> - Works without JavaScript — content visible even if JS is disabled or slow to load
- 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
| Pillar | Explanation |
|---|---|
| Dynamic Content | Show different data per user, per request — product pages, dashboards, profiles |
| Reusable Layouts | Define header, footer, navbar once; include them across every page |
| Data Binding | Pass a JavaScript object from the route; the template renders it as HTML |
| Separation of Concerns | Logic stays in routes/controllers; presentation stays in template files |
5. Popular Template Engine Options
| Engine | Syntax Style | File Extension | Example |
|---|---|---|---|
| EJS | HTML + <%= %> tags | .ejs | <h1><%= title %></h1> |
| Pug (Jade) | Indentation-based, no closing tags | .pug | h1= title |
| Handlebars | HTML + {{ }} mustache syntax | .hbs | <h1>{{title}}</h1> |
| Nunjucks | Jinja2-inspired {% %} blocks | .njk | <h1>{{ title }}</h1> |
| Mustache | Logic-less {{ }} | .mustache | <h1>{{title}}</h1> |
6. Detailed Comparison
| Feature | EJS | Pug | Handlebars | Nunjucks |
|---|---|---|---|---|
| Learning curve | Low (just HTML) | Medium (new syntax) | Low | Medium |
| Full JS in templates | Yes — any JS expression | Yes | No (logic-less) | Limited |
| Looks like HTML | Yes | No (indentation) | Yes | Yes |
| Partials / Includes | <%- include() %> | include keyword | {{> partial}} | {% include %} |
| Layout inheritance | Via package | Built-in extends | Via package | Built-in extends |
| npm weekly downloads | ~15M+ | ~8M+ | ~10M+ | ~2M+ |
| Best for | Beginners, quick SSR | Clean minimal syntax | Logic-less templates | Python 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
| Aspect | Template Engine (EJS, Pug) | SPA Framework (React, Vue, Angular) |
|---|---|---|
| Rendering | Server builds HTML | Browser builds HTML from JS |
| First load | Fast (HTML ready) | Slower (download JS, then render) |
| Interactivity | Page reload per action | Instant UI updates, no reload |
| SEO | Excellent by default | Requires SSR/SSG setup (Next.js, Nuxt) |
| Complexity | Simple | Higher (state mgmt, routing, build tools) |
| Use case | Blogs, dashboards, admin panels, MVPs | Complex interactive apps, SPAs |
| Data flow | Server -> template -> browser | API -> 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
- A template engine generates HTML by merging a template file with a data object on the server.
- This process is called server-side rendering (SSR) — the browser gets finished HTML.
- EJS uses HTML syntax with
<%= %>tags — no new language to learn. - Template engines are the View in MVC; they never contain business logic or database queries.
- Use template engines for content sites, admin panels, MVPs; use SPA frameworks for highly interactive apps.
Explain-It Challenge
Explain without notes:
- What is the difference between server-side rendering and client-side rendering? Name one advantage of each.
- Why would you pick EJS over Pug for a team of beginners?
- 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 ->