Serving Static Files with Node.js

In this article we’re going to look at setting up a static file server using Node. There’s plenty of modules available to help us do this, but we’re going to focus on three of them: node-static, paperboy, and http-server.

All of these modules are available via npm (Node Package Manager). npm has been packaged with node since version 0.6.3, but if you haven’t got it installed it’s easy enough to do with this one-liner:

curl http://npmjs.org/install.sh | sh

Let’s have a look at each static file server in turn, starting with node-static.

node-static

node-static can be installed via npm with the following command:

npm install node-static

To use it: include the module in your script via the require function, create a server, pass it your preferred options, and tell it to serve files:

var static = require('node-static'),
  http = require('http'),
  util = require('util');

var webroot = './public',
  port = 8080;

var file = new(static.Server)(webroot, { 
  cache: 600, 
  headers: { 'X-Powered-By': 'node-static' } 
});

http.createServer(function(req, res) {
  req.addListener('end', function() {
    file.serve(req, res, function(err, result) {
      if (err) {
        console.error('Error serving %s - %s', req.url, err.message);
        if (err.status === 404 || err.status === 500) {
          file.serveFile(util.format('/%d.html', err.status), err.status, {}, req, res);
        } else {
          res.writeHead(err.status, err.headers);
          res.end();
        }
      } else {
        console.log('%s - %s', req.url, res.message); 
      }
    });
  });
}).listen(port);

console.log('node-static running at http://localhost:%d', port);

Here, when we create a new instance of a node-static server, we pass it:

  • the directory we want it to serve files from by way of the `webroot` variable (node-static defaults to serving files from the current directory unless you tell it otherwise)
  • a cache value of 600, so each file served will be cached for 10 minutes (the default value for cache is 3600, meaning files are cached for 1 hour)
  • a custom ‘X-Powered-By’ header (I’ve used X-Powered-By solely as an example – you may, or may not, want to disclose the software you’re server is running)

We then use the http module’s createServer function, and add an event handler for the request’s end event where we use our instance of node-static to serve the files.

When we call file.serve, we pass it an optional callback function that let’s us customise how we handle any errors:

  • we check err.status, and serve either 404.html or 500.html accordingly
  • if there’s an error and the status code is something other than 404 or 500, we send the status code and the headers back to the client explicitly – when you pass a callback function to file.serve and there is an error, node-static will not respond to the client by itself.

paperboy

paperboy provides a fluent API for setting up your server that lets you configure the port and IP address, set HTTP headers, and handle events pre-request and post-response. You can also tell it what to do in case of a error. Let’s see how we might use paperboy to create a static file server:

var paperboy = require('paperboy'),
    http = require('http'),
    path = require('path');

var webroot = path.join(__dirname, 'public'),
    port = 8080;

http.createServer(function(req, res) {
  var ip = req.connection.remoteAddress;
  paperboy
    .deliver(webroot, req, res)
    .addHeader('X-Powered-By', 'Atari')
    .before(function() {
      console.log('Request received for ' + req.url);
    })
    .after(function(statusCode) {
      console.log(statusCode + ' - ' + req.url + ' ' + ip);
    })
    .error(function(statusCode, msg) {
      console.log([statusCode, msg, req.url, ip].join(' '));
      res.writeHead(statusCode, { 'Content-Type': 'text/plain' });
      res.end('Error [' + statusCode + ']');
    })
    .otherwise(function(err) {
      console.log([404, err, req.url, ip].join(' '));
      res.writeHead(404, { 'Content-Type': 'text/plain' });
      res.end('Error 404: File not found');
    });
}).listen(port);

console.log('paperboy on his round at http://localhost:' + port);

After importing the modules we require, we use a couple of variables to store the webroot we want documents served from, and the port we want our server to listen on. Note that the current version of paperboy (0.0.3) doesn’t appear to support a webroot specified with a relative filepath e.g. ./public. We therefore use path.join(__dirname, ‘public’) to give us the absolute path to the public folder we want to serve files from (__dirname is one of Node’s globals and gives us the name of the directory that the currently executing script lives in).

We pass webroot to the deliver function so paperboy knows which directory to serve files from. addHeader then adds some HTTP headers. It takes two arguments: the name of the header, and the header’s value. We then use the before, after, error, and otherwise functions to add event handlers via some callback functions:

  • before will fire it’s callback function just before the requested file is delivered. To stop the file being served you can return false from the callback function.
  • after will fire it’s callback function once a response has been sent back to the client and the file has been delivered. The callback function is passed a single argument representing the status code of the HTTP response sent back to the client
  • error fires if something goes wrong during the processing of the request. The callback is passed two arguments: the HTTP status code of the response, and the error message. Note that you’ll need to close the connection to the server yourself in your error callback function; the example above does this with the line response.end(…).
  • otherwise fires if no matching file was found in webroot, or if false was returned from the before callback function. The callback function is passed a single argument containing details of the error. paperboy’s default callback function shows a simple HTTP 404 File Not Found page.

Lastly we tell the server which port to listen on. Note that when it receives a request for ‘/’, paperboy appends index.html and serves that file by default, as long as index.html exists.

http-server

Finally, lets have a look at http-server. Installation is done via npm, but make sure you pass npm the -g switch to make http-server available globally:

npm install http-server -g

Installing http-server this way makes it available via the command line:

$> type http-server
http-server is hashed (/usr/local/bin/http-server)

As it’s available from the command line, you won’t have to write any code to get it up and running. All you have to do is type:

$> http-server
Starting up http-server, serving ./public on port: 8080
http-server successfully started: http://localhost:8080
Hit CTRL-C to stop the server

This will start the serving files out of the ./public directory if it exists, or the current working directory if it doesn’t. The default port used by http-server is 8080, but you can change this, and a few other options, via command line switches:

  • -p to specify which port to listen on
  • -a to tell it which IP address to bind to
  • -i to configure whether to display autoIndex (defaults to true)
  • -s or –silent to turn off console logging
  • -h or –help to display this list of available commands

In this article, we’ve looked at three static file server modules available in Node: node-server, paperboy, and http-server. While they all achieve the same goal, which one you choose to use might be down to how much code you want to write, and how much control you need to exercise, over your Node static file server.

Of course, you might decide to write your own static file server from scratch. If you do, you could do a lot worse than to peruse the source code of the three servers we’ve looked at today:

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • http://www.udgwebdev.com Caio Ribeiro Pereira

    Nice post man!!

  • Andy

    None of these modules allows you to set a different cache time per file type nor can you gzip compress the files before returning to the client.

    Or am I missing something?

    • http://ianoxley.com Ian Oxley

      No, you’re right: it doesn’t appear that there’s any support for setting different cache times per file type, or gzip support.

      For a static file server though, would you really need to specify different cache times per file type? The files are all static after all, although I can see why you might want this for CSS, client-side JavaScript and images.

      Gzip support isn’t available right now, but there are some open issues requesting it for node-static (https://github.com/cloudhead/node-static/issues/20) and paperboy (https://github.com/felixge/node-paperboy/issues/25).

      • Andy

        The files are static yes, but the point of sending cache times is to prevent a trip to the server at all (as efficient as that trip may be, it will obviously never beat no trip at all).

        Take a look at the HTML5 boilerplate for a good starting guide to cache times (along with cache-busting techniques for the longer expiries):
        cache.appcache, html, xml, json -> 0 seconds
        rss -> 1 hour
        favicon -> 1 week
        images, video, audio, htc, webfonts -> 1 month
        css, js -> 1 year

        This is the sort of control that is necessary.

  • http://www.autonopedia.org Mik Fielding

    I seem to be missing something here, er… why do you need tools to serve static pages? They were the standard type of page before CMS. A well designed static page loads fast and generally have the advantage of being served to virtually any browser.

    That horrible pile of shite puked out by Microsoft has a tendency (even in the later versions) of warning users about such thing as js for serving pages and a lot of average users actually end up avoiding such pages. Perhaps someone could enlighten me…

    • http://ianoxley.com Ian Oxley

      node-static, paperboy, and http-server are all web servers, written in JavaScript, that are designed to serve static files. All the code in the article runs on the server – none of the code runs client-side in the browser, so users shouldn’t get any warnings.

      Hope this helps. If I’ve misunderstood what you weren’t sure about though please let me know.