Static Files
Arbor provides two utilities for serving files: staticDirectory and sendFile. Both stream files directly using Node's filesystem APIs and work inside normal action chains. Static serving is explicit and predictable.
staticDirectory
staticDirectory exposes a directory through a wildcard route. The route must end in /** so that params.wild contains the requested file path.
import { createRoute, staticDirectory } from "@kequtech/arbor";
const actionStatic = staticDirectory({
location: "/public",
index: ["index.html"],
contentTypes: {
".3gp": "audio/3gpp",
},
});
const routeStatic = createRoute({
method: "GET",
url: "/assets/**",
actions: [
actionStatic,
],
});
How it resolves files
- The directory root is computed once from
process.cwd()and thelocationoption. - The
wildparameter is interpreted as a relative path under this root. - If
wildattempts to escape the root (such as via../../), Arbor throwsEx.Forbidden. - If the resolved path is a directory and an index list is provided, Arbor returns the first matching index file.
- If the resolved path is a file, it is returned.
- Otherwise Arbor throws
Ex.NotFound.
This ensures predictable resolution without exposing unintended parts of the filesystem.
Content-Type resolution
staticDirectory determines the content type using a non-exhaustive list of common file extensions. You can provide custom extensions through contentTypes.
contentTypes: {
".svgz": "image/svg+xml",
}
Error behavior
- Missing files →
NotFound - Invalid paths or traversal →
Forbidden - Valid file but stream failure → handled by
sendFile(InternalServerError)
All thrown errors enter Arbor’s normal error-handling pipeline.
GET and HEAD
staticDirectory supports HEAD automatically because sendFile finalizes HEAD responses without writing a body.
Prep actions
You can add your own actions before the generated action:
const actionCache = createAction(({ res }) => {
res.setHeader("Cache-Control", "public, max-age=3600");
});
const routeStatic = createRoute({
method: "GET",
url: "/assets/**",
actions: [
actionCache,
actionStatic,
],
});
This lets you control caching, cookies, authentication, or other behavior.
sendFile
Use sendFile when you want to explicitly stream a single file inside of an action.
const routeDownload = createRoute({
method: "GET",
url: "/download/config.json",
actions: [
async ({ req, res }) => {
await sendFile(req, res, "/data/config.json", "application/json");
},
],
});
Behavior
sendFile:
- Resolves the path under
process.cwd(). - Ensures the path refers to a file via
stat(). - Sets
Content-Type(guessed or explicit). - Sets
Content-Length. - On HEAD, ends immediately.
- On GET, streams the file and finalizes the response.
Error behavior
- File not found → NotFound
- Not a file → NotFound
- Stream error → InternalServerError
Bypassing renderers
A successful sendFile call finalizes the response directly. No renderer runs afterward.
Summary
staticDirectory
- Maps requests to files under a directory
- Protects against path traversal
- Supports index files
- Guesses MIME types
- Integrates with normal actions
sendFile
- Streams a single file
- Supports custom MIME types
- Throws an error on failure
- Finalizes the response when successful
Both tools are small, explicit and predictable, matching Arbor’s overall design philosophy.