We'll need some files to serve. Let's create a directory named content
, containing the following three files:
index.html
styles.css
script.js
Add the following code to the HTML file index.html
:
Add the following code to the script.js
JavaScript file:
And finally, add the following code to the CSS file style.css
:
As in the previous recipe, we'll be using the core modules http
and path
. We'll also need to access the filesystem, so we'll require fs
as well. With the help of the following code, let's create the server and use the path
module to check if a file exists:
If we haven't already done it, then we can initialize our server.js
file by running the following command:
Try loading localhost:8080/foo
. The console will say foo doesn't exist, because it doesn't. The localhost:8080/script.js
URL will tell us that script.js
is there, because it is. Before we can serve a file, we are supposed to let the client know the content-type
header, which we can determine from the file extension. So let's make a quick map using an object as follows:
We could extend our mimeTypes
map later to support more types.
Modern browsers may be able to interpret certain mime types (like text/javascript
), without the server sending a content-type
header, but older browsers or less common mime types will rely upon the correct content-type
header being sent from the server.
Remember to place mimeTypes
outside of the server callback, since we don't want to initialize the same object on every client request. If the requested file exists, we can convert our file extension into a content-type
header by feeding path.extname
into mimeTypes
and then pass our retrieved content-type
to response.writeHead
. If the requested file doesn't exist, we'll write out a 404
error and end the response as follows:
At the moment, there is still no content sent to the client. We have to get this content from our file, so we wrap the response handling in an fs.readFile
method callback as follows:
Before we finish, let's apply some error handling to our fs.readFile
callback as follows:
Note
Notice that return
stays outside of the fs.readFile
callback. We are returning from the fs.exists
callback to prevent further code execution (for example, sending the 404
error). Placing a return
statement in an if
statement is similar to using an else
branch. However, the pattern of the return
statement inside the if
loop is encouraged instead of if else
, as it eliminates a level of nesting. Nesting can be particularly prevalent in Node due to performing a lot of asynchronous tasks, which tend to use callback functions.
So, now we can navigate to localhost:8080
, which will serve our index.html
file. The index.html
file makes calls to our script.js
and styles.css
files, which our server also delivers with appropriate mime types. We can see the result in the following screenshot:
This recipe serves to illustrate the fundamentals of serving static files. Remember, this is not an efficient solution! In a real world situation, we don't want to make an I/O call every time a request hits the server; this is very costly especially with larger files. In the following recipes, we'll learn better ways of serving static files.
Our script creates a server and declares a variable called lookup
. We assign a value to lookup
using the double pipe ||
(OR) operator. This defines a default route if path.basename
is empty. Then we pass lookup
to a new variable that we named f
in order to prepend our content
directory to the intended filename. Next, we run f
through the fs.exists
method and check the exist
parameter in our callback to see if the file is there. If the file does exist, we read it asynchronously using fs.readFile
. If there is a problem accessing the file, we write a 500
server error, end the response, and return from the fs.readFile
callback. We can test the error-handling functionality by removing read permissions from index.html
as follows:
Doing so will cause the server to throw the 500
server error status code. To set things right again, run the following command:
Note
chmod
is a Unix-type system-specific command. If we are using Windows, there's no need to set file permissions in this case.
As long as we can access the file, we grab the content-type
header using our handy mimeTypes
mapping object, write the headers, end the response with data loaded from the file, and finally return from the function. If the requested file does not exist, we bypass all this logic, write a 404
error message, and end the response.