Reference · Files and processes

file

signature
file(str, map | null=) -> response

What it does

A response that sends a file from the program's folder, for a route to answer with: route GET /report = file("exports/report.pdf"). It serves the one file it is given, whatever its name, so a path from the visitor goes to files instead, which does not serve .env, .git/, the program's own files or a database: route GET /assets/*path = files("public", path). The content type comes from the extension, the response carries an ETag and Last-Modified, and a browser that asks with a current copy gets a 304 with no body. A file that is not there, a folder, or a path with .. or starting at / gives a 404 response, not an error. The options map takes cache: seconds, or a duration like "10m", "12h", "30d", "1y", which lets the browser keep the file that long without asking; without it the answer says no-cache (keep it, but ask each time). It also takes download, a file name, and type, a content type in place of the extension's. The file goes out as it is on disk: an HTML file gets none of the scripts Quillo adds to the pages a route makes, so its ETag and a Range of it are of the file's own bytes. For a whole folder served as a static host serves it (index.html, /about for about.html, 404.html), see files.

Examples

in a program
route GET /report = file("exports/report.pdf")route GET /favicon.svg = file("public/favicon.svg", { cache: "30d" })route GET /assets/*path = files("public", path)                  // a path from the visitorroute GET /build/*path = files("dist", path, { cache: "1y" })     // names that change with content

The value can be looked at like any response: its status, headers and body.

write_file("public/app.css", "body { margin: 0 }")let r = file("public/app.css")print(r)print(r.status, r.headers["content-type"], r.headers["cache-control"])print(r.body)print(file("public/app.css", { cache: "1y" }).headers["cache-control"])
output
<response 200>200 text/css; charset=utf-8 no-cachebody { margin: 0 }public, max-age=31536000, immutable

Edge cases

write_file("data.bin", "x")print(file("nope.txt").status, file("nope.txt").body)print(file("../secret").status, file("/etc/hosts").status, file("public").status)print(is_err(file("nope.txt")))print(file("data.bin").headers["content-type"])print(file("data.bin", { cache: 60 }).headers["cache-control"])
output
404 not found404 404 404falseapplication/octet-streampublic, max-age=60, immutable

Errors

A path that is not a string, options that are not a map, a cache that is neither seconds nor a duration, an empty type or download, and a type with a line break or another control character are runtime errors, not error values. The options are only looked at for a file that exists: file("nope", { cache: "soon" }) is a plain 404. A download name is written so a name in any script arrives whole; its control characters are not sent as they are.

this one fails
write_file("a.txt", "a")print(file("a.txt", { cache: "soon" }))
output
line 2: `file`'s cache is seconds or a duration like "30d", got "soon"
this one fails
write_file("a.txt", "a")print(file("a.txt", { type: "" }))
output
line 2: `file`'s type is empty: give a content type, like "text/csv", or leave it out

Chaining

In a pipeline the path comes first; the options follow in the call:

write_file("public/app.js", "1")let path = "app.js"print("public/" + path | file)print(("public/" + path | file({ cache: "1y" })).headers["cache-control"])
output
<response 200>public, max-age=31536000, immutable

See also

read_file, write_file, respond, redirect, image.convert

Thread
7Useful? Like it, or add to its thread below.

Thread 0 posts

On file(): questions, tips, corrections, and code that uses it.

No posts yet. The first one opens the thread.

    Edit this page on GitHub · every example's output was made by running it (tools/refcheck.mjs) · questions on Discord or r/quillo