Course outline

Express.js / Intermediate: modules, identity and adapters / Lesson 11

Static files, templates and download contracts

Keep public assets distinct from authorized file delivery and escape template output for its actual context.

About 4 min readContent reviewed
On this page

Lesson goals

  • Choose static roots and path ownership without exposing private directories.
  • Explain template escaping, download names and response completion boundaries.

A static directory is a publication decision

express.static serves files under a configured filesystem root. Treat every eligible file placed there as intentionally public under the selected options; it is not a per-object document authorization system. Keep uploads, secrets, private exports and source metadata outside that root. Use a deliberate absolute root rather than relying on a process working directory that can change between launch environments. Middleware order determines whether an asset request falls through to another handler. Review dotfile behavior explicitly, especially during Express 5 migration, rather than broadening access when a hidden file no longer appears.

Mount only an approved public root

This fragment receives an already reviewed asset root and mounts it at /assets. The options illustrate refusing dotfiles and limiting how missing files fall through; evaluate them against the project’s intended behavior. A private export should instead be located through an authorized domain lookup and a safe source identifier, not by joining a user path onto the static directory. A Content-Disposition download filename is presentation metadata, not permission. Neither an obscure filename nor a forced download makes a private file safe to publish under a shared static root.

javascript Illustrative scoped static middleware
app.use("/assets", express.static(publicAssetRoot,{
  dotfiles:"deny",
  fallthrough:false
}));

publicAssetRoot is an injected reviewed absolute directory. No files are read and no static server is started here.

Escaping depends on where text is inserted

Express integrates with template engines but does not define one universal escaping language. A value safe for HTML text is not automatically safe inside a script, URL or attribute context. Choose the engine’s documented escaped output form and keep untrusted content away from raw rendering constructs. Avoid letting users choose a template filename or inject arbitrary template locals that alter rendering options. res.locals should contain an intentional view model, not the whole request or internal entity. Validate links and context-specific encodings separately from ordinary text escaping.

Treat a download label as a narrow display value

The complete local model chooses a synthetic ASCII download label from a validated opaque export ID. It does not use caller-provided paths, and it never opens a file. A real file-serving adapter must still authorize the export, resolve its storage identity safely and use its framework’s header serialization. This model deliberately avoids pretending that path normalization alone prevents every filesystem traversal or symbolic-link issue. Distinguish the storage location, public display name and object eligibility; merging those concepts is how a harmless-looking filename parameter becomes an unintended read capability.

javascript Complete offline download-label model
function exportLabel(id) {
  if (typeof id !== "string" || !/^e[0-9]{1,6}$/.test(id)) throw new Error("INVALID_EXPORT_ID");
  return `course-export-${id}.csv`;
}
console.assert(exportLabel("e17") === "course-export-e17.csv");
let rejected=false; try {exportLabel("../private");} catch {rejected=true;}
console.assert(rejected);

e17 produces a fixed-format label and the path-like input is rejected. Storage lookup, authorization and header encoding remain untested.

Handle missing files and late failures honestly

A file can disappear between metadata lookup and delivery, or a client can disconnect after headers have been sent. Use the documented sendFile/download completion contract and forward errors appropriately without attempting a second response. Track cancellation and release resources when delivery ends. Review media type, content disposition, caching and authorization together for each document category. Test missing, denied and partial delivery in an isolated authorized integration. The local course validates only the label function; it does not exercise templates, disk serving, browser interpretation or a real file transfer.

Common errors and fixes

Private exports appear under the asset URL

Why it happens
Private storage was placed inside a public static root.
How to fix it
Separate storage and serve exports through authorized object lookup.
Prevent it next time
Review the publication status of every directory under the root.

User content reaches a raw template slot

Why it happens
Escaping was bypassed or assumed context-independent.
How to fix it
Use the engine’s appropriate escaped form and safe view model.
Prevent it next time
Test text, attribute and URL contexts separately.

A late download error causes another response

Why it happens
The delivery commitment boundary was ignored.
How to fix it
Follow completion/error callbacks and headersSent handling.
Prevent it next time
Test disconnect and missing-file races in isolation.

Check your understanding

1. Why is a download filename not a storage authorization token?

Show worked solution

It describes presentation to the client, while the server must independently identify an eligible object and resolve its storage location. A friendly or obscure name establishes neither ownership nor permitted read scope. Keep object IDs, storage keys and display labels separate, and use documented header serialization rather than concatenating untrusted text.

2. Does HTML text escaping protect a value placed inside JavaScript?

Show worked solution

Not generally. Different contexts have different parsing rules. Avoid embedding untrusted values directly in executable code and follow the template engine’s documented safe data-transfer mechanism. A test of ordinary HTML text cannot certify script, attribute or URL contexts, so name the exact context in the review.

Key takeaways

  • Static publication and authorized document delivery need different ownership.
  • Output encoding and file completion follow the actual context and lifecycle.

Official references