Serving Static Files from Node.js

This obnoxiously colored box is here to tell you that this is not the post you are looking for. I don’t think that it is horrible but it is old and gives you an example of something you should probably not be doing. If you want to serve static files, this post is probably much better for you .

In the last post I showed you how to get started with Node.js on Windows . Easy, wasn’t it? Remarkably so since there was no install requirement. This time we are going to tweak the system a bit then learn how to return static files like plain ol’ Html, Css and Javascript files.

Note, for posterity's sake, that all of this is based on node.js as of version 0.4.7. If I had to bet money, I'd bet that things will change. So, dear future reader, this post may be out of date by the time you read this.

Making Things Easier

Last time we got started by unzipping the file that contained all the node files. That worked great but it would be nice to be able to have multiple node sites and not have to copy those files around. Let’s make that easy by copying those files to somewhere else (like “C:\Program Files\Node”) and adding a Path entry for it.


Computer 
    > Right-click and hit properties 
    > Advanced System Settings (on the left) 
    > Environment Variables 
    > System Variables 
    > Path 
    > add “C:\Program Files\Node”

So now the files that we use can be in a directory all their own and you still only have to type “node.exe server.js” to get started. Woot!

And another tip, keep Fiddler open while you mess around with node. It is very helpful to be able to see what was returned with each request. If you haven’t used Fiddler, you are missing out! Download it.

Serving Up A File

So starting a server and returning some Html hard-coded in Javascript is awesome like we did last time, but it would be even better if we could do this by serving up Html files. Let’s do that. Now, in IIS land we would expect to just be able to start the server and it would server up static files like css, js and html but that’s not the default behavior of node. Like I said in the last post, it’s raw, it’s low level and unless you use a pre-made node package (talk about that some other time) you get to do it all yourself. So let’s pick up ourselves by our own bootstraps and get this thing done.

node.js does a lot for you

First, let’s create a very basic html page. Maybe something like this:


<html>
	<head>
		<title>Rockin' Page</title>
        </head>
	<body>
		<p>This is a page. For realz, yo.</p>
	</body>
</html>

We’ll start by adding a reference to the “fs” (FileSystem) module. This is for file i/o. Below we’ll interogate the url passed in to decide which static file to return but for now we’ll just return the one created above. Line 8 shows how to read the file from disk:


var http = require('http');
var fs = require('fs');
http.createServer(function (request, response) {
    console.log('request starting...');
	
	fs.readFile('./index.htm', function(error, content) {
		if (error) {
			response.writeHead(500);
			response.end();
		}
		else {
			response.writeHead(200, { 'Content-Type': 'text/html' });
			response.end(content, 'utf-8');
		}
	});
	
}).listen(8125);
console.log('Server running at http://127.0.0.1:8125/');

The first parameter I pass is the path to the file. The beginning “./” starts the path off at the root, then follows the file name. The second is the callback that fires when the file has finished reading from disk. The callback will return an error (if one happened) or the contents of the file. If there is an error, I’m returning a 500. Otherwise I’m returning the file. Sweet.

Serving Up Files

And this would be completely sufficient if we were serving up a single page website that had no includes…which is rare. So we should handle being able to return various static files. Let’s do this in two stages. First, let’s handle looking at the url to decide which file is being requested and return that. We will also import and use the path module to make sure a file exists before we try to read it. For grins, we’ll also add in a reference to a javascript (jquery) and css file and hook them up to see if they work.


var http = require('http');
var path = require('path');
var fs = require('fs');
http.createServer(function (request, response) {
    console.log('request starting...');
	
	var filePath = '.' + request.url;
	if (filePath == './')
		filePath = './index.htm';
	
	path.exists(filePath, function(exists) {
	
		if (exists) {
			fs.readFile(filePath, function(error, content) {
				if (error) {
					response.writeHead(500);
					response.end();
				}
				else {
					response.writeHead(200, { 'Content-Type': 'text/html' });
					response.end(content, 'utf-8');
				}
			});
		}
		else {
			response.writeHead(404);
			response.end();
		}
	});
	
}).listen(8125);
console.log('Server running at http://127.0.0.1:8125/');

<html>
	<head>
		<title>Rockin' Page</title>
		<link type="text/css" rel="stylesheet" href="style.css" />
		<script type="text/javascript" src="jquery-1_6.js"></script>	
        </head>
	<body>
		<p>This is a page. For realz, yo.</p>
	</body>
	<script type="text/javascript">
		$(document).ready(function() {
			alert('happenin');
		});
	</script>
</html>

Okay, so that’s cool and all but you might have noticed a problem: we are serving up .js and .css files as content type ‘text/html’. That’s just not cool. Let’s accommodate that by setting the content type by the extension of the file.


var http = require('http');
var fs = require('fs');
var path = require('path');
http.createServer(function (request, response) {
    console.log('request starting...');
	
	var filePath = '.' + request.url;
	if (filePath == './')
		filePath = './index.htm';
		
	var extname = path.extname(filePath);
	var contentType = 'text/html';
	switch (extname) {
		case '.js':
			contentType = 'text/javascript';
			break;
		case '.css':
			contentType = 'text/css';
			break;
	}
	
	path.exists(filePath, function(exists) {
	
		if (exists) {
			fs.readFile(filePath, function(error, content) {
				if (error) {
					response.writeHead(500);
					response.end();
				}
				else {
					response.writeHead(200, { 'Content-Type': contentType });
					response.end(content, 'utf-8');
				}
			});
		}
		else {
			response.writeHead(404);
			response.end();
		}
	});
	
}).listen(8125);
console.log('Server running at http://127.0.0.1:8125/');

And voila! Everything is being served up as it should.

What’s Next? Next we’ll talk about writing files. It’s easy. We’ll even receiving some json and using that for writing our files. Stay tuned!

Comments

Ido 2011-05-23 04:14:45

Email: WebsiteUrl: Text: This is exactly what I was looking for. I spent so much time searching for such a simple solution and nobody was able to figure it out. Thanks!

Marak 2011-05-23 04:50:40

WebsiteUrl: Text:

I appreciate you blogging about Node.js, but this post is probably going to hurt more then it helps.

1. You should mention some of the many ( well developed ) static file modules already available
2. You shouldn't be doing a fs.readFile on every http request, this is a terrible design
3. You should be using the .pipe() api for static file serving

If you'd like more help / information I suggest you join the #Node.js room on irc.freenode.net

mikeal 2011-05-23 05:01:48

WebsiteUrl: Text:

dude, you gotta use streaming. you're bring the entire contents of the file in to memory and then pushing it out the network, you should really stream the file from disk to the socket.

also, you probably wanna check for the GET method. this is off the top of my head.

var basepath = '/files'

http.createServer(function (req, resp) {
if (req.method !== 'GET') {
req.writeHead(400);
req.end();
return;
}
var s = fs.createReadStream(path.join(basepath, req.path));
s.on('error', function () {
req.writeHead(404);
req.end();
})
s.once('fd', function () {req.writeHead(200);});
s.pipe(resp);
})

Eric Sowell 2011-05-24 12:19:46

WebsiteUrl: Text:

@Ido - Glad you found it useful.



@Marak - 1. Perhaps I shall. 2. Agree. Also not claiming that I'm creating the next big and best file server. 3. I'll look into that.



I'm experimenting, seeing what works and what doesn't. I'm too much of a Node noob to think this is more than that, and perhaps I need more disclaimer in that regard. I'm not against using other packages but at the moment I'm just interested in messing around with the core bits. And thanks for the tips, I may drop by and lurk on #Node.js.



@mikeal - I will look into that. Looks interesting and makes sense. Thanks.

Hay 2011-06-30 08:36:36

WebsiteUrl: Text:

I would recommend using something like Connect instead of the raw API. Makes serving static files a lot easier.

http://senchalabs.github.com/connect/

Eric 2011-07-07 12:45:43

WebsiteUrl: Text:

I will look into that one. I'm messing around with other things at the moment but will be circling back around to nodejs here pretty soon. Thanks for the tip.

Eli 2011-09-20 08:24:58

WebsiteUrl: Text:

Excellent article.

To the point and fast :D

I'm falling in love with Node.js

John 2011-10-07 12:49:30

Email: WebsiteUrl: Text:

While I agree that in practice you would use a robust, existing library for this, rather than writing your own, this post was extremely useful to me as fellow Node.js newb who is interested in learning about the core Node offerings. When I someday use this in a real project, I will look at the Node ecosystem as a whole.

Thanks for a great introductory post, Eric!

Alec 2011-10-16 08:24:04

WebsiteUrl: Text:

Finally someone who managed to speak nongeek. Thanks be. I have my 'sandbox', such as it is, up and running.

Eduardo Costa 2011-10-25 02:43:58

WebsiteUrl: Text:

The Small Question of Doom(tm): what happens if someone sends you evil paths, like "../"? Example: curl http://myserver:8125/../../../../etc/passwd

borg 2011-11-15 02:57:22

WebsiteUrl: Text:

how about serving audio and video tag? any hints??

Tomnar 2011-11-30 06:48:33

WebsiteUrl: Text:

I'm new to node.js, but shouldn't the contentType-check be inside the else where the response is sent?

It didn't work for me having it outside the else, because it was changing my content-types before getting the callback ready for sending the content. So my .css files started to get sent as text/javascript and so on.

Steven 2012-03-12 05:54:27

Text:

Thanks man, this is great, I'm just playing around with node.js at the moment and having an understanding of how to do this at a low level is really useful. Even if I choose to go with a library like connect I'd still like to know how this can be implemented at a low level.

Cheers,
Steven

Scon 2012-04-06 02:52:46

Text:

some syntax sugar:

var dictTypes = {
  '.js': 'text/javascript',
  '.css': 'text/css',
}
...
var contentType = dictTypes[extname] || 'text/html';

http file server 2012-04-19 03:14:31

Text:

I recommend you to take a look at this http file server. You can set up your own file server for managing and sharing files through web browser. It's like DropBox but self-hosted so that you can keep all your confidential files on your own server. The web based UI looks and feels like Windows 7 Explorer. It offers features that are not possible with a FTP server such as zipping files, downloading multiple files and folders in single download etc. It's also easier to set up and administrate than a FTP server.

baris 2012-07-05 02:51:27

WebsiteUrl: Text:

thank you, it works!

Brian Hurlow 2012-07-13 03:48:41

Email: Text:

Just so everyone knows, the connect middleware framework supports a really easy static file server module so you don't have to do this.

SESO 2012-08-31 02:17:43

WebsiteUrl: Text:

Thanks a lot, I've found this kind of info, but nobody tell me what I want. Thanks again :D

Kabkee 2012-09-29 04:13:57

WebsiteUrl: Text:

IT IS what i've been looking for :)

So much appreciated.

saeid 2012-10-30 02:22:39

Text:

You are excellent Eric,goooooooooo...od, go on like this.

Todd B. 2013-04-27 04:55:08

Text:

This was lifesaver for me. I needed to serve up a local .js file and the default example wasn't working. This was just the ticket. I didn't mention that I needed to run this web server on a Beaglebone not connect to the Internet; the <script src ="http://..." worked, but the <script src="myfile.js" did not and I was pulling my hair out trying to find a simple fix. Many thanks for folks like you that post good examples for the rest of us to learn off of.