Serve Static
The @wooksjs/http-static package provides the serveFile function, which allows you to serve static files in Wooks HTTP. It returns a readable stream from the file system.
Features:
- Returns a readable stream
- Prepares all the neccessary response headers (like content-length, content-type etc)
- Can handle etag
- Can handle ranges
Installation
To use the static file serving functionality, you need to install the @wooksjs/http-static package:
npm install @wooksjs/http-staticUsage
Once installed, you can import the serveFile function and use it in your Wooks application.
Example:
import { serveFile } from '@wooksjs/http-static';
app.get('static/file.txt', () => {
// ...
return serveFile('file.txt', { baseDir: 'public' });
});The serveFile function takes the file path as the first argument and accepts an optional options object as the second argument. It returns a readable stream of the file content.
Options
The options object allows you to customize the behavior of the file serving. It provides the following properties:
headers: ARecord<string, string>of additional response headers to set, in any casing; they replace the computed ones (e.g.Content-Type), exceptContent-LengthandContent-Range, which always describe the bytes sent.cacheControl: An object of Cache-Control directives (e.g.{ maxAge: '10m', public: true });maxAge/sMaxageaccept seconds or time strings.expires: The Expires header value to specify the expiration date/time of the file.pragmaNoCache: A boolean value indicating whether to add the Pragma: no-cache header.baseDir: The base directory path for resolving the file path. Defaults to the process working directory (process.cwd()); it is also the boundary for the path-traversal check.defaultExt: The default file extension appended when the path has none (e.g.defaultExt: 'html'servesaboutasabout.html). The fallback is tried once, then cleared, so it does not recurse indefinitely.listDirectory: A boolean value indicating whether to render an HTML directory listing when the path is a directory.index: The filename of the index file to automatically serve when the path is a directory (e.g.'index.html'). If the URL has no trailing slash, the request is first redirected (302) to add one.allowDotDot: Whentrue, allows../parent-directory traversal in the requested path. Defaults tofalse— leave it off unless you fully control the input, as enabling it lets a crafted path escapebaseDir.
Path traversal
With the default allowDotDot: false, any path that resolves outside baseDir is rejected with a 403 before touching the filesystem — ../ segments that stay inside baseDir are still allowed. Only enable allowDotDot for trusted, server-controlled paths — never for a segment taken straight from the URL (get('*')).
Built-in file server example:
Here's an example of using the serveFile function to create a built-in file server:
import { useRouteParams } from '@wooksjs/event-http';
import { serveFile } from '@wooksjs/http-static';
app.get('static/*', () => {
const { get } = useRouteParams();
return serveFile(get('*'), { cacheControl: { maxAge: '10m' } });
});See the Cache Control documentation for details on configuring cache directives.
Conditional Requests & Ranges
serveFile handles HTTP caching and partial transfers automatically — you don't wire anything up:
- ETag / Last-Modified. Every file response gets an
ETag(derived from inode, size, and mtime) and aLast-Modifiedheader. On the next request the browser sendsIf-None-Match/If-Modified-Since; when the file is unchanged,serveFileresponds304 Not Modifiedwith no body. The rules are the same as for every response (Conditional GET): weakIf-None-Matchcomparison with lists and*, a strict HTTP-date forIf-Modified-Sincecompared at whole seconds, and preconditions checked beforeRange. The304carriesETag,Last-Modifiedand your cache headers.If-Match(strong comparison) andIf-Unmodified-Sinceare checked first: a stale one answers412 Precondition Failedfor any method. A412or416is an error response: it carriesCache-Control: no-storeinstead of your cache options, so caches never keep it. For a method other than GET/HEAD (whenserveFileis mounted withapp.all), a matchingIf-None-Matchalso answers412. - Range requests. For a GET with a
Rangeheader,serveFilestreams only the requested byte range with206 Partial Contentand aContent-Rangeheader:bytes=a-b,bytes=a-and the suffix formbytes=-n(the last n bytes). A range starting past the end (orbytes=-0, or any range on an empty file) answers416withContent-Range: bytes */<size>. The unit is case-insensitive (BYTES=0-1works). Several ranges, another unit, or invalid syntax are ignored and the whole file is sent (200), as RFC 9110 allows. Other methods, HEAD included, ignoreRange. AnIf-Rangeheader is honored with strong comparison: the range is sent only when it names this exactETag(a weakW/tag never matches) or the exactLast-Modifieddate of a file not modified within the current second (a date is weak while another change in that second would carry it); otherwise the full file is sent, whatever theRangesays. Every file response advertisesAccept-Ranges: bytes.
These make serveFile suitable for large downloads, video/audio seeking, and resumable transfers without extra configuration.