Episode 3 — NodeJS MongoDB Backend Architecture / 3.5 — Template Engine EJS
Interview Questions: Template Engine EJS (Episode 3)
How to use this material (instructions)
- Read
3.5.athrough3.5.e. - Answer aloud, then compare below.
- Pair with
3.5-Exercise-Questions.md.
Q1. What is a template engine and why would you use one?
Why interviewers ask: Tests understanding of server-side rendering fundamentals.
Model answer:
A template engine takes an HTML template file with special placeholder tags and merges it with a data object to produce a complete HTML page on the server. This is called server-side rendering (SSR). You would use one when you need dynamic content (user dashboards, product pages) with good SEO, fast first paint, and a simpler architecture than a full SPA framework. Template engines also enforce separation of concerns — logic stays in route handlers, presentation stays in templates. In Express, you configure it with app.set('view engine', 'ejs') and render pages with res.render('view', data).
Q2. What is the difference between <%= %> and <%- %> in EJS?
Why interviewers ask: Directly tests security awareness (XSS prevention).
Model answer:
<%= expression %> outputs the result with HTML entities escaped — characters like <, >, ", ', and & are converted to their safe equivalents (<, >, etc.). This prevents cross-site scripting (XSS) attacks. If a user submits <script>alert('hack')</script> as their name, <%= name %> renders it as visible text, not executable code.
<%- expression %> outputs the result raw with no escaping. HTML tags in the value become real DOM elements. This is required for including partials (<%- include('header') %>) and rendering trusted HTML from a CMS. It must never be used with unsanitized user input.
Rule of thumb: Default to <%= %>. Only use <%- %> for includes and content you have explicitly sanitized.
Q3. How do you handle a variable that might not be passed to the template?
Why interviewers ask: Tests practical EJS debugging knowledge — ReferenceError is a common beginner trap.
Model answer:
If you write <% if (message) { %> and message was never passed by res.render(), EJS throws a ReferenceError because message is not declared in scope. The fix is to use the locals object: <% if (locals.message) { %>. The locals object is always defined — it is the data object passed to res.render(). Accessing a property that does not exist on an object returns undefined (no error), whereas accessing an undeclared variable in scope throws.
This pattern is essential for flash messages, error alerts, and optional data that some routes provide and others do not.
Q4. Explain the difference between server-side rendering and client-side rendering.
Why interviewers ask: Fundamental architecture question for full-stack roles.
Model answer:
With server-side rendering (SSR), the server builds the complete HTML document — the browser receives a finished page and paints it immediately. Template engines like EJS, Pug, and Handlebars do this. Benefits include faster first paint, better SEO (crawlers see real content), and simpler architecture.
With client-side rendering (CSR), the server sends a minimal HTML shell and a JavaScript bundle. The browser downloads the JS, executes it, and the framework (React, Vue, Angular) builds the DOM dynamically. Benefits include rich interactivity, no page reloads, and offline capability (with service workers).
Trade-offs: SSR is better for content-heavy sites and SEO. CSR is better for complex interactive UIs. Modern frameworks like Next.js and Nuxt combine both — SSR for the initial load, then CSR for subsequent navigation.
Q5. What are partials in EJS and why are they important?
Why interviewers ask: Tests understanding of code reuse and DRY principle in templates.
Model answer:
Partials are small, reusable EJS files that contain a fragment of HTML — typically a header, footer, navbar, card, or alert component. You include them with <%- include('partials/header') %>. Partials are important because they enforce the DRY principle — you write the navigation bar once and include it in every page. Changing a link in the navbar partial updates every page automatically. You can also pass data to partials: <%- include('partials/card', { item: product }).
Partials use <%- %> (unescaped) because they contain HTML that must be rendered as real DOM elements, not escaped text.
Q6. How do you serve CSS, JavaScript, and images alongside EJS templates?
Why interviewers ask: Tests practical Express knowledge — static files are essential for any real project.
Model answer:
Express provides the built-in express.static middleware. You configure it with app.use(express.static(path.join(__dirname, 'public'))). This makes everything inside the public/ folder accessible via URL. A file at public/css/style.css is available at /css/style.css — the public/ prefix is stripped from the URL.
In EJS templates, you link to these files with absolute paths: <link rel="stylesheet" href="/css/style.css">. Absolute paths are critical — relative paths like css/style.css break on nested routes like /admin/users because the browser resolves them relative to the current URL path.
The middleware should be placed before route definitions so it can serve static files without hitting the router.
Q7. Compare EJS with Pug (Jade). When would you choose each?
Why interviewers ask: Tests awareness of the template engine ecosystem.
Model answer:
EJS uses HTML syntax with <%= %> tags embedded inline. It has a very low learning curve — any developer who knows HTML can read and write EJS immediately. It supports full JavaScript expressions in templates.
Pug uses a whitespace-based indentation syntax with no angle brackets or closing tags. It is more concise but requires learning a new syntax. Pug has built-in layout inheritance (extends, block) that EJS does not have natively.
Choose EJS when your team includes designers familiar with HTML, when onboarding speed matters, or for simpler projects. Choose Pug when you want cleaner template syntax, built-in layouts, and the team is willing to learn a new DSL. Both produce identical HTML output.
Q8. What is the role of template engines in the MVC architecture?
Why interviewers ask: Tests understanding of software architecture patterns.
Model answer:
Template engines are the View layer in MVC. The Model handles data and database operations. The Controller (Express route handler) receives the request, calls the model for data, and passes that data to the view. The View (EJS template) renders the data as HTML and returns it to the controller, which sends it as the HTTP response.
The view should never query the database or contain business logic. The controller should never build HTML strings. This separation makes each layer independently testable and replaceable — you could swap EJS for Pug without touching any route handlers, as long as the data shapes remain the same.
Q9. How does express-ejs-layouts work?
Why interviewers ask: Tests knowledge of practical layout patterns.
Model answer:
The express-ejs-layouts package adds layout inheritance to EJS. You define a master layout file (e.g., views/layouts/main.ejs) that contains the complete HTML structure — <!DOCTYPE>, <head>, navbar, footer. Where the page-specific content should go, you write <%- body %>.
Each page template (e.g., home.ejs) then contains only its unique content — no doctype, no head, no includes. The package automatically wraps each rendered page inside the layout.
Setup requires three lines: npm install express-ejs-layouts, app.use(expressLayouts), and app.set('layout', 'layouts/main'). This is cleaner than the manual header/footer include pattern because page files are shorter and there is no risk of forgetting an include.
Q10. What security considerations exist when using EJS?
Why interviewers ask: Security awareness is critical for backend developers.
Model answer:
The primary concern is XSS (Cross-Site Scripting). Always use <%= %> (escaped output) for user-generated content — form submissions, database records, query parameters. Never use <%- %> (raw output) for untrusted data. Even with escaped output, be cautious with data inside HTML attributes (href, onclick, style) where JavaScript injection is possible even without <script> tags.
Other considerations: do not pass sensitive data (API keys, passwords) to templates — they appear in the HTML source. Do not use templates for business logic — keep validation and authorization in route handlers. For rich text from users (WYSIWYG editors), use a sanitization library like DOMPurify before storing or rendering.
Quick-fire
| # | Question | One-line |
|---|---|---|
| 1 | Default EJS output tag | <%= %> (escaped) |
| 2 | Raw output tag | <%- %> (unescaped) |
| 3 | Logic-only tag | <% %> (scriptlet) |
| 4 | Include a partial | <%- include('path') %> |
| 5 | Check optional variable | locals.varName |
| 6 | Static files middleware | express.static('public') |
| 7 | Template in MVC | View layer |
| 8 | Failed to lookup view | File not in views folder or misspelled |
<- Back to 3.5 -- README