How to make a Node.js static file server - Part 1
This multi-part series will take you through everything from how to get a basic Node.js static file server going, to different way’s to deliver the content to the user, common pitfalls, caching, and when to use Node.js to serve files vs other solutions.
I’m going to assume you already have Node.js up running on your computer and that you know javascript. This code was developed on v0.6.1-pre and I will make an effort to update it and the version number as things change with Node.js.
First thing your going to do is create a new file which will house our server code.
touch server.js
There are a few native Node.js modules that we’ll need and a few more will be added as this series continues.
var http = require('http'),
path = require('path'),
fs = require('fs'),
util = require('util');
We are pulling in the following modules:
- HTTP: Low level API that handles the parsing of streams and handling of the HTTP messages.
- Path: A string manipulation library, also allows you to check the file system for the existence of a file.
- File System: Provides simple wrappers around standard POSIX functions for file IO.
- Utilities: Extra utility features.
Create the server
Within the scope of this part of the tutorial we only need to access the HTTP module once for its createServer() method. We create a HTTP server which calls the _handler
callback when a request event is made and start listening on a specified port.
http.createServer(_handler).listen(3000);
console.log('Server running at http://127.0.0.1:3000/');
Now set up the request handler which will house all of the code that handles each and every request that comes into your server. We’ll also define some variables which we’ll use later.
function _handler(req, res) {
var root = "..",
url = "",
contentType = "text/plain",
filePath = "";
//All following code goes here
}
Because this is strictly a file server we only want to support the GET method and reject any other requests. To do this we need to reference the request object passed to our callback, an instance of http.ServerRequest stored in req
if (req.method !== 'GET') { //If the request method doesn't equal 'GET'
res.writeHead(405); //Write the HTTP status to the response head
res.end('Unsupported request method', 'utf8'); //End and send the response
return;
}
On most web servers when you send a request to the server without a file you will get some type of default file usually the index.html. Because this is a file server, if they aren’t requesting a file, they are going to get an error. After we determine that they are requesting a file we do simple string concatenation to build the file path and then pass it to the path.exists() method. This method will test the file path for existence and then pass that information to the callback, serveRequestedFile
in this case.
if ('.' + req.url !== './') {
filePath = root + req.url;
path.exists(filePath, serveRequestedFile);
} else {
res.writeHead(400);
res.end('A file must be requested', 'utf8');
return;
}
Stream your files
Now that we have set up the http server, verified that they are using the correct method, and that they are requesting a file, we need to serve that file to them. To do this we will use our previously mentioned serveRequestedFile()
function which will receive the status of our file inquiry which we need to test before proceeding.
function serveRequestedFile(file) {
if (file === false) {
res.writeHead(404);
res.end();
return;
}
//Following code will go here
}
Because we aren’t implementing any caching mechanism yet we don’t want to read the whole file into memory before serving. To get around that we use the File System’s fs.createReadStrem() method. This method returns a Readable Stream which emits a number of events. We will set up an error event handler in the event there is a server error and the file cannot be served to the user.
var stream = fs.createReadStream(filePath);
stream.on('error', function(error) {
res.writeHead(500);
res.end();
return;
});
Before we send the data to the user we should set the files mime type. For simplicity sake I have included the mime object within the function itself. On a true file server you would likely import a much larger mime list.
var mimeTypes = {
'.js' : 'text/javascript',
'.css' : 'text/css',
'.gif' : 'image/gif'
}
contentType = mimeTypes[path.extname(filePath)];
We have now verified that the file exists, created a stream instance for the file in question, set up basic error handling, and found the files mime type. The only thing left is to actually send the file to the user. We first set the content type and the status in he header then use Utilities util.pump() method to push the previously initiated stream to the user. The pump method reads the data from the readable stream stream
and sends it to the writable stream res
. If there is an error in this process or the stream is closed the third arugment, the callback, is called.
res.setHeader('Content-Type', contentType);
res.writeHead(200);
util.pump(stream, res, function(error) {
//Only called when the res is closed or an error occurs
res.end();
return;
});
Start the server and start serving files!
node server.js
I hope you enjoyed the first part of this series and were able to get your own file server up and running. I have included a working example of the above code if you want to see it in its entirety. If you need further help feel free to comment below, mention me on twitter, or join #Node.js on irc.freenode.net.