Serving Static Files with Node.js

    Ian Oxley
    Share

    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: