Views and Static Files
By Syncfusion®, Inc. · Last updated
EJS templates, passing data, loops, conditions, partials and safe escaping, then serving CSS, images and scripts with express.static, caching, SPA fallbacks and keeping private files safe.
What you will learn
- Render EJS templates with data
- Use loops, conditions and partials
- Escape output to stop XSS
- Serve static files with express.static
- Set caching and keep private files safe
Server-Side Rendering Introduction
Server-side rendering (SSR) = the server builds the full HTML page before sending it.
- The browser gets a finished page. It does not need to build it.
- Express builds pages from templates and data.
- Template = an HTML file with gaps for data.
- View engine = the tool that fills the gaps. This course uses EJS.
res.render("menu", data)fillsviews/menu.ejsand sends the HTML.
Simple lesson: A template plus data makes a page.
A Real-World Example
Every school report card has the same printed layout.
- The layout has empty boxes: name, class, marks.
- The office fills in one student's details.
- Each student gets the same layout with different details.
- Nobody draws a new report card from scratch.
The printed layout is the template. The details are the data. Filling the boxes is rendering.
In this lesson, you will learn:
- How Express builds HTML pages from EJS templates and data.
- Loops, conditions, partials and safe escaping.
- Custom view engines, and when server-side rendering fits.
- How to serve a folder with
express.static. - Mount paths, absolute paths and caching options.
- How to serve a built frontend app safely.
Static HTML vs Dynamic HTML
- Static HTML = the same file for everyone. See the other part of this lesson.
- Dynamic HTML = built per request, with today's data.
- The canteen's "About us" page can be static.
- The canteen's "Today's menu" page changes daily, so it is dynamic.
Animation: a template and data go into res.render, and a finished HTML page comes out
views/menu.ejs
data ["Idli", "Dosa"]
res.render()
HTML page
The template and the data meet in res.render. A finished page comes out.
Simple lesson: Static pages never change. Rendered pages change with the data.
Setting Up a View Engine
On your own computer, install EJS first:
npm install ejs
- app.set("view engine", "ejs") = use EJS for files without an extension.
- app.set("views", folder) = where templates live. Default:
viewsin the project. - View file extension:
.ejs.res.render("menu")findsviews/menu.ejs. - You do not need to
import ejs. Express loads it from the setting.
In this lab, EJS is already installed. On your own computer, npm install ejs adds it to package.json.
- The response panel shows the rendered page. Press Source to see the HTML.
<%= school %>was replaced byGreen Valley.
Simple lesson: Set the view engine, put templates in views/, call res.render.
EJS Tags
<%= value %>= print a value, escaped (safe).<%- value %>= print a value unescaped (raw HTML).<% code %>= run JavaScript, such asifand loops. Prints nothing.<%# note %>= a comment. Not sent to the browser.-%>= also removes the line break after the tag.
Simple lesson: <%= prints safely, <% runs code.
Passing Data to Templates
The template can read data from three places, merged together:
- app.locals: shared by every page, such as the school name.
- res.locals: for this request, such as the logged-in user.
- The object in res.render: for this page, such as the menu.
- If two have the same name, the
res.renderobject wins.
schoolcomes fromapp.locals,studentfromres.locals.- The
forEachloop prints one<li>per item. ?veg=trueturns on the note and filters the list.
Simple lesson: Loops and if go in <% %>. Values go in <%= %>.
Reusable Views
Every page needs the same header and footer.
- Partial = a small template used inside others, such as
partials/header.ejs. - Include =
<%- include("partials/header") %>puts a partial in place. - Use
<%-for includes, because the partial is HTML you trust. - You can pass data:
include("partials/header", { title: "Menu" }). - Layout = the shared page shape. In EJS, you build it with a header and a footer partial.
views/
├── home.ejs
├── about.ejs
└── partials/
├── header.ejs
└── footer.ejs
Simple lesson: Write the header once, include it everywhere.
Template Safety
Some data comes from users, such as a name or a review.
- A user might type
<script>alert("hi")</script>as their name. <%= name %>escapes it, so the browser shows the text and runs nothing.<%- name %>prints it raw, so the script would run. This attack is XSS.- Use
<%-only for HTML you wrote yourself, like includes. - Never build a template from user text. Only pass user text as data.
- Press Source. The safe line shows
<script>. The unsafe line has a real<script>tag. - The preview here blocks scripts. A real browser would run it.
Default to <%= %>. Every <%- %> with user data is a security hole.
Simple lesson: Escape user data. Always.
Template Errors and Custom Engines
- A missing template gives
Failed to lookup view. Express sends it to your error handler. - A typo inside a template, like an unknown variable, is also an error.
- app.engine(ext, fn) = registers your own engine for a file extension.
- The function gets
(filePath, options, callback)and calls back with the HTML. - Express-compatible engines, such as EJS and Pug, already provide this function.
/fills the gaps with your own engine./missingshows the "Failed to lookup view" error.
Simple lesson: app.engine connects any template language to res.render.
Choosing a Template Engine
- EJS: plain HTML with
<% %>tags. Easiest if you know HTML. Used in this course. - Pug: short, indentation-based syntax. No closing tags.
- Handlebars (express-handlebars):
{{ }}tags, very little logic in templates. - All three escape output by default.
- Pick one per project and stick with it.
When to Use Server-Side Rendering
- Good for: content sites, blogs, admin pages, simple forms, pages that search engines must read.
- Good because: the first page shows fast, and it works without much browser JavaScript.
- Less good for: very interactive apps, like a live chat or a drawing tool.
- Limits: every click may reload a full page, and the server does more work.
- Many teams use React or Vue for rich apps, and Express only for the API.
Easy way to remember: content pages = SSR, app-like pages = a frontend framework plus an Express API.
Simple lesson: SSR is simple and fast to start. Choose it for content, not for complex apps.
Static Files
Static files = files sent exactly as they are saved.
- CSS files, browser JavaScript, images, fonts and PDFs are static.
- The server does not change them per request.
- Dynamic responses are built per request, such as
res.jsonorres.render. - express.static(folder) = built-in middleware that serves a whole folder.
- The usual folder name is
public.
Simple lesson: Put files in public/, and express.static hands them out.
A Second Example
The canteen has a notice board and a kitchen.
- The notice board holds printed papers: the timetable, the price list.
- Anyone can walk up and read them. Nobody cooks anything.
- The kitchen makes each order fresh.
- The staff never let visitors into the kitchen store room.
public/ is the notice board. Your routes are the kitchen. Your source code is the store room.
Serving a Public Folder
app.use(express.static("public"))serves everything inpublic.public/style.cssis served at/style.css. The folder name is not in the URL.public/index.htmlis served at/. This is the index file.- Express picks the
Content-Typefrom the file extension. - A missing file passes on to the next middleware, usually ending in 404.
- Open Headers for each file and compare
Content-Type. /nothing.pngfalls through to the normal 404.
Simple lesson: The URL path matches the file's path inside public.
Mount Paths and Virtual Prefixes
- Mount path:
app.use("/assets", express.static("public")). - Now
public/style.cssis at/assets/style.css. - Virtual prefix = a URL prefix that is not a real folder name.
- It keeps file URLs apart from your routes and API paths.
- Change the prefix later without moving any files.
/assets/style.cssworks./style.cssnow gives 404.
Simple lesson: A mount path adds a prefix to every static URL.
Multiple Folders and Absolute Paths
- You can register more than one static folder.
- Express checks them in order. The first folder with the file wins.
- Relative path:
"public"is relative to where you runnode. - Run
nodefrom another folder, and"public"points to the wrong place. - Absolute path:
path.join(import.meta.dirname, "public")always points next to your code file. import.meta.dirnameneeds Node.js 20.11 or later.
/banner.txtcomes fromfestival, because it is checked first./rules.txtonly exists inpublic, so that folder answers.- Remove the festival line after Diwali. The normal banner comes back.
Easy way to remember: always build static paths with path.join(import.meta.dirname, "public").
Simple lesson: Order decides which folder wins. Absolute paths stop surprises.
Static Middleware Options
Pass options as the second argument: express.static("public", { maxAge: "1d" }).
- index: the file for a folder URL. Default
"index.html".falseturns it off. - extensions: try these endings, so
/aboutfindsabout.html. - dotfiles: files starting with a dot. Default
"ignore": never served. - fallthrough:
true(default) passes misses on.falsesends an error. - maxAge: how long browsers may cache files, in milliseconds or text like
"1d". - immutable: tells browsers the file will never change.
- etag and lastModified: help browsers check if their copy is still fresh. Both on by default.
- redirect: adds a trailing
/to folder URLs. On by default. - setHeaders: a function to add your own headers per file.
/aboutfindsabout.htmlthanks toextensions.- Look at
Cache-Controlunder Headers:max-age=86400. /.envgives 404, because dotfiles are ignored by default.
Simple lesson: Options control file lookup, caching and extra headers.
Caching Static Files
Caching = the browser keeps a copy so it does not download the file again.
- Cache-Control: max-age = how many seconds the copy stays fresh.
- ETag = a short fingerprint of the file. If it matches, the server answers 304 Not Modified.
- Short cache for files that change under the same name, like
style.css. - Long cache + immutable for files with a version in the name, like
app.3f9a1c.js. - Build tools such as Vite add these versions for you.
In this lab, the browser cache is not used, so every request reaches the server. Real browsers send If-None-Match with the ETag and may get a quick 304 instead of the whole file.
Simple lesson: Versioned file names can be cached for a year. Plain names should not be.
Serving a Built Frontend
A React or Vue app builds into a folder, often dist.
- Serve
distwithexpress.static. - Put API routes first, under
/api. - Then add a fallback route that sends
index.htmlfor any other GET. - The fallback lets the frontend router handle URLs like
/orders/7. - In Express 5, the fallback path is
"/*splat", not"*".
/orders/7is not a file, so it getsindex.html./api/nopegets a JSON 404, not the HTML page. API mistakes stay clear.
Simple lesson: API first, static files second, index.html fallback last.
Static File Safety
- Never serve your project root:
express.static(".")exposesserver.jsandpackage.json. - Keep only public files in
public. No.env, no database files, no notes. - Dotfiles are ignored by default. Do not change
dotfilesto"allow". - Express blocks
..in paths, so visitors cannot climb out of the folder. - Private files, like student report cards, need a route with a login check. See File Handling.
/server.jssends your source code, password and all./.envis still blocked, but that is luck, not good design.- Change
"."to"public"and create apublicfolder.
Anything inside a static folder is public. Ask "would I print this on the notice board?" before you put it there.
Simple lesson: Serve one folder made for the public, never the whole project.
Common mistakes
| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
No view engine set | Express knows .ejs | Set it, or write res.render("menu.ejs") | No default engine was specified |
Template outside views/ | Express searches everywhere | It looks in the views setting | Failed to lookup view |
<%- userName %> | Raw is the same as escaped | Raw prints HTML as code | XSS attacks |
<%= include(...) %> | Includes are values | Includes are HTML, use <%- | The header shows as text |
Forgetting npm install ejs | Express bundles EJS | It is a separate package | Cannot find module 'ejs' |
Per-user data in app.locals | It is per request | It is shared by everyone | One user's name shows to all |
<link href="/public/style.css"> | The folder name is in the URL | Mount path + file path only | 404 for every asset |
express.static("public") with a relative path | It is relative to the code file | It is relative to where node started | Files missing when started elsewhere |
express.static(".") | Convenient during development | It exposes source code | Secrets leak |
| SPA fallback before API routes | Order does not matter | API routes must come first | API calls get HTML |
app.get("*", ...) fallback | Express 4 syntax still works | Use "/*splat" in Express 5 | TypeError at startup |
Long cache on style.css | Caching is always good | Only for versioned names | Users see old styles for days |
Check Yourself
Exercises
What to Remember
- SSR builds HTML on the server from a template plus data.
app.set("view engine", "ejs")and templates inviews/.res.render(view, data)fills the template and sends the page.<%= %>prints safely,<% %>runs code,<%- %>is only for trusted HTML.- Use partials and
includefor shared headers and footers. express.static("public")serves a folder. The folder name is not in the URL.- A mount path like
/assetsadds a prefix. - Options control index files, extensions, dotfiles and caching.
- Never serve the project root. Only public files go in
public.
Next: Input Processing and Validation. You will check and clean every value a user sends.