Skip to content


Subversion checkout URL

You can clone with
Download ZIP
A node module for serving static files. Does etags, caching, etc.

Fetching latest commit…

Cannot retrieve the latest commit at this time

Failed to load latest commit information.


A module for serving static files. Does etags, caching, etc.


Very simple usage examples:

var st = require('st')
var http = require('http')

// Just serve the files in '/static' under the url '/static'

Pass some options to the st function, and it returns a handler function.

That handler function will return true if it handles the static request, or false if it doesn't. (This is so that you can only serve static files if they're in /static for example.)

Here are some options if you want to configure stuff. All of these are optional except path which tells it where to get stuff from.

If you pass a string instead of an object, then it'll use the string as the path.

If you don't specify a url, then it'll mount on the portion of the resolved path that is above process.cwd(). For example, st('./foo') will serve the files in /path/to/cwd/foo/* if the user requests*.

Here are all the options described with their defaults values and a few possible settings you might choose to use:

var st = require('st')
var mount = st({
  path: 'resources/static/',
  url: 'static/', // defaults to path option

  cache: { // specify cache:false to turn off caching entirely
    fd: {
      max: 1000, // number of fd's to hang on to
      maxAge: 1000*60*60, // amount of ms before fd's expire

    stat: {
      max: 5000, // number of stat objects to hang on to
      maxAge: 1000 * 60, // number of ms that stats are good for

    content: {
      max: 1024*1024*64, // how much memory to use on caching contents
      maxAge: 1000 * 60 * 10, // how long to cache contents for

    index: { // irrelevant if not using index:true
      max: 1024 * 8, // how many bytes of autoindex html to cache
      maxAge: 1000 * 60 * 10, // how long to store it for

    readdir: { // irrelevant if not using index:true
      max: 1000, // how many dir entries to cache
      maxAge: 1000 * 60 * 10, // how long to cache them for

  // indexing options
  index: true, // auto-index, the default
  index: 'index.html', // use 'index.html' file as the index
  index: false, // return 404's for directories

  dot: false, // default: return 403 for any url with a dot-file part
  dot: true, // allow dot-files to be fetched normally

  passthrough: true, // calls next instead of returning a 404 error
  passthrough: false, // returns a 404 when a file or an index is not found

// with bare node.js
http.createServer(function (req, res) {
  if (mount(req, res)) return // serving a static file
  myCustomLogic(req, res)

// with express
// or
app.route('/static/:fooblz', function (req, res, next) {
  mount(req, res, next) // will call next() if it doesn't do anything

On the command line:

$ st -h
Static file server in node


-h --help             Show this help

-p --port PORT        Listen on PORT (default=1337)

-d --dir DIRECTORY    Serve the contents of DIRECTORY (default=cwd)

-i --index [INDEX]    Use the specified INDEX filename as the result
                      when a directory is requested.  Set to "true"
                      to turn autoindexing on, or "false" to turn it
                      off.  If no INDEX is provided, then it will turn
                      autoindexing on.  (default=true)

-ni --no-index        Same as "--index false"

-. --dot [DOT]        Allow .files to be served.  Set to "false" to

-n. --no-dot          Same as "--dot false"

-nc --no-cache        Turn off all caching.

-a --age AGE          Max age (in ms) of cache entries.

Range Requests

Range requests are not supported.

I'd love a patch to add support for them, but the spec is kind of confusing, and it's not always a clear win if you're not serving very large files, so it should come with some very comprehensive tests.

Thankfully, as far as I can tell, it's always safe to serve the entire file to a request with a range header, so st does behave correctly, if not ideally in those situations. It'd be great to be able to do the better thing if the contents are cached, but still serve the full file if it's not in cache (so that it can be cached for subsequent requests).

Memory Caching

To make things go as fast as possible, it is a good idea to set the cache limits as high as you can afford, given the amount of memory on your server. Serving buffers out of process memory will generally always be faster than hitting the file system.

Client Caching

An etag header and last-modified will be attached to every request. If presented with an if-none-match or if-modified-since, then it'll return a 304 in the appropriate conditions.

The etag is generated based on the dev, ino, and last modified date. Stat results are cached.


If the request header claims to enjoy gzip encoding, and the filename does not end in '.gz' or '.tgz', then the response will be gzipped.

Gzipped bytes are not included in the calculation of cache sizes, so this utility will use a bit more memory than the cache.content.max and cache.index.max bytes would seem to allow. This will be less than double, and usually insignificant for normal web assets, but is important to consider if memory is at a premium.

Filtering Output

If you want to do some fancy stuff to the file before sending it, you can attach a res.filter = myFilterStream thing to the response object before passing it to the mount function.

This is useful if you want to get the benefits of caching and gzipping and such, but serve stylus files as css, for example.

Something went wrong with that request. Please try again.