3.2 · Template engines & serving files

Level 3 · Express & databases

3.2Template engines & serving files

Goal: serve static files (express.static) and generate dynamic HTML with a template engine (res.render), while placing SSR vs separate-front API.
Estimated time: 10 min · self-paced reading · If you only have 5 min: read “The essentials in 30s” + the diagram.

The essentials in 30 seconds

  • Static files served in one line: app.use(express.static('public')) delivers CSS, images and client JS from a dedicated folder.
  • Dynamic content rendered with an engine: EJS/Handlebars/Pug templates + res.render('view', { data }) = server-assembled HTML (SSR).
  • Two valid architectures: SSR for content and SEO, JSON API + separate front end for interactive apps — the next levels lean towards the API.

Running case — Salim publishes the catalogue in SSR then as an API

Salim, junior developer in Algiers, must publish the catalogue: product pages in HTML for search referencing, plus a highly interactive internal dashboard. He first serves CSS and images with express.static('public'), configures EJS (app.set('view engine', 'ejs')) and renders res.render('produit', { produit }) for the SSR pages. For the dashboard, he exposes the same data as res.json(): the dedicated front end will consume the API. One server, two renderings according to use.

This same case returns in the lesson, the diagram, the script and the 48h challenge.

1. Static files & templates

A web app delivers fixed resources (CSS, images, client JS, fonts): Express serves them with the built-in middleware express.static() — app.use(express.static('public')) exposes the whole public/ folder (a request to /style.css returns public/style.css). Assets are grouped in this dedicated folder.

For dynamic HTML, use a template engine: a template (HTML + variables, loops, conditions) filled with data. The common ones: EJS (JS in <% %> tags, close to HTML), Handlebars/hbs ({{ }} syntax, clean), Pug (indented, concise). Configure it (app.set('view engine', 'ejs')), store views in views/, render with res.render('page', { data }). This is SSR (Server-Side Rendering): the server builds the complete HTML and sends it to the browser.

2. SSR or API + separate front end: decide by project

SSR suits content sites (showcases, blogs, classic e-commerce), SEO (ready HTML, easily indexable) and fast initial loading. The modern alternative: Node exposes a JSON REST API (section 4-1) consumed by a separate front end (React, Vue, Angular) rendering in the browser — ideal for interactive apps (dashboards, tools) and a smooth reload-free experience.

Both are valid: Salim chooses SSR for the pages (content, SEO) and API + front end for the dashboard (interactivity). And SSR is not outdated: it is coming back strongly via Next.js/Nuxt combining server rendering and interactivity. The next levels lean towards the API/JSON approach, dominant on the Node side.

Common misinterpretation: “SSR is outdated, everything must be done as an SPA, and express.static also serves templates”. No. SSR stays relevant (content, SEO, simplicity) and enjoys a modern revival; an SPA needlessly complicates a simple showcase site. And express.static only serves the fixed files of public/, never the views of views/ rendered by res.render().
Static, SSR or API? One server, three outputs according to need Staticexpress.static: CSS, images+SSRres.render: complete HTML+API + front endres.json: data for React/Vue Content/SEO → SSR; interactive app → API + front end.
One server, three outputs according to need
Text version of the diagram (SEO / screen readers / translation): 1) Static: Salim puts CSS and images in public/ served by express.static. 2) SSR: EJS renders the product page as complete HTML for SEO. 3) API: the same data goes out as res.json() to the dashboard. Lesson: choose the rendering by use, not by fashion.

🔎 Concrete example — Serve, then render, then expose as JSON

Salim covers all three outputs with the same server.

app.use(express.static('public'));
app.set('view engine', 'ejs');
app.get('/produits/:id', (req, res) => {
  res.render('produit', { produit: { id: req.params.id } });
});
app.get('/api/produits/:id', (req, res) => {
  res.json({ id: req.params.id });
});

Reading: same data, two renderings: HTML via EJS or JSON via res.json().

Lesson: comparing both renderings makes the SSR vs API difference tangible.

Section vocabulary

Static files
Fixed resources (CSS, images, client JS, fonts) served as-is by express.static('public').

Simple example: Salim’s style.css served from public/.

What it is not: an EJS view rendered with data.

Reflex: group all assets in public/.

Template engine
Tool generating dynamic HTML from templates (variables, loops, conditions): EJS, Handlebars, Pug.

Simple example: Salim’s EJS product page looping over reviews.

What it is not: a mere folder of fixed files.

Reflex: choose EJS to start, close to HTML.

res.render()
Express method rendering a view: res.render('page', { data }) injects data into the views/ template.

Simple example: res.render('produit', { produit }) at Salim’s.

What it is not: sending raw JSON to the front end.

Reflex: name the view like the views/ file.

SSR
Server-Side Rendering: the server generates the complete HTML and sends it to the browser; a content and SEO asset.

Simple example: Salim’s indexable catalogue pages.

What it is not: an SPA rendering everything in the browser.

Reflex: prefer SSR for pages to be referenced.

API + separate front end
Node exposes JSON (REST API) consumed by a React/Vue/Angular front end rendering in the browser.

Simple example: The internal dashboard fed with res.json().

What it is not: an EJS template rendered server-side.

Reflex: reserve this architecture for interactive apps.

In practice — Serve files and generate HTML

  1. Serve static files with app.use(express.static('public')): place a CSS file and an image in public/ and check they are reachable.
  2. Configure a template engine (e.g. EJS: app.set('view engine', 'ejs')) and place templates in views/.
  3. Render a view with data: res.render('page', { titre: '...', items: [...] }); in the template, insert the variables and loop over a list.
  4. Compare: render the same data as JSON (res.json(data), API approach) to feel the SSR vs API + separate front end difference.
You serve static files (express.static) and generate dynamic HTML with a template engine (res.render), while placing SSR vs API + separate front end.

Ready-to-say script — justifying SSR vs API (30 s)

“Catalogue pages go out as SSR with EJS: complete HTML, good for SEO and first paint. The dashboard, highly interactive, consumes the same data as JSON through our API. Static CSS and images are served by express.static from public.”

Say when arbitrating architecture; show the same data as HTML then as JSON.

Copy-paste template — static + EJS + JSON

const express = require('express');
const path = require('path');
const app = express();
app.use(express.static('public'));
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'views'));
app.get('/produits/:id', (req, res) => {
  res.render('produit', { id: req.params.id });
});
app.get('/api/produits/:id', (req, res) => {
  res.json({ id: req.params.id });
});
app.listen(3000);

Freelancer

A showcase site is fine in SSR + static: no heavy front end for a few pages.

SME in Algiers

Salim renders the pages in EJS for SEO and keeps the JSON API for the internal admin.

Product team

Separate views/ (SSR) and /api (JSON) so two teams progress without conflict.

Beginner

This week: one CSS file served statically and one EJS view with a loop over three items.

✓ Best practices

  • Serve assets with express.static (one public/ folder).
  • Choose the architecture by project (content/SEO vs interactive app).
  • Master the REST API (JSON): the dominant approach of the next levels.

✗ Common pitfalls

  • Believing SSR “outdated” (it is reviving: Next.js/Nuxt).
  • Choosing an SPA for a simple content site (needless complexity).
  • Neglecting SEO in a pure-SPA architecture.

Key takeaways

  • STATIC SERVING: app.use(express.static('public')) serves fixed resources (CSS, images, client JS).
  • TEMPLATE ENGINES (EJS, Handlebars, Pug): dynamic HTML via res.render('view', {data}).
  • SSR: the server generates the complete HTML — the approach of classic sites (content, SEO).
  • Widespread modern alternative: Node exposes a REST API (JSON) consumed by a SEPARATE FRONT END (React/Vue/Angular).

48h challenge — apply tomorrow

Serve 1 CSS file statically and render 1 EJS view with a loop over 3 items.

Section exit checklist
  • I serve a public/ folder without hesitating.
  • I render a view with res.render().
  • I decide SSR vs API by project.
Self-rating 1-5: are my views in views/ and my assets in public/?can I justify SSR or API for a need?

Frequently asked questions

SSR (templates) or API + separate front end: which architecture to choose?

Both are valid depending on the project. SSR suits content sites (blogs, showcases, classic e-commerce), when SEO matters (HTML delivered ready), for fast initial loading and barely interactive sites (simpler). API + separate front end suits interactive apps (dashboards, tools), a smooth reload-free experience and front/back separation. Salim mixes: SSR for the pages, API for the dashboard.

Is server-side rendering outdated compared to React?

No, it is even enjoying a revival. After the classic SSR era (EJS templates), then the SPA craze (everything rendered client-side, with its SEO and initial-loading limits), frameworks like Next.js and Nuxt combine both: server-rendered HTML (SEO, speed) + client interactivity (SPA experience). Classic SSR stays relevant for many sites through its simplicity.

Check your understanding

Which Express method renders a view with data?

Additional resources (optional, to go further)

Level 3 examFresh, harder questions to test what you've learned