Learning Node.js, Part 2

Here I’ll continue the Node.js learning process by starting to construct a very simple server that will serve up static resources, like HTML pages.

Let’s expand the examples we started with in the first post to do something a little more interesting. Let’s serve a really minimal website consisting of a “Home” page and an “About this Site” page. We’ll also serve up the equivalent of a custom 404 error page. For now, we’ll stick with my original example style and just serve plain text instead of HTML. Create a file called testServer.js and put the following in it:

Here I’m using the createServer() method and, within that, I’m using a simple switch mechanism based on the value of the url property of the req (http.ServerRequest) object. If a root route (“/”) is passed in as the request or an “/about” route is passed in as a request, then plain text content will be returned to the browser. If anything else is used as part of the URL, a “Not Found” message, and 404 response, will be sent. This is largely just a variation on what we were doing in the first post, but here I’m gently introducing you to the concept of routing.

Start up your server:

$ nodemon testServer.js

Do note here that I’m running nodemon (see my first post in this series) so that I don’t have to keep restarting the server any time I make changes to my file. If you aren’t using that, start the server with node testServer.js.

Notice that this logic works for the following URLs:

The first two return a page with the text “Home Page” in it. The third returns a page with “About Page” in it. The last one in that list (“/crypto”) returns a “Not Found” (404) message and that makes sense because I have no “route” (case statement) set up for that. However, you’ll find that the logic in place doesn’t work for this URL:

That URL will return the “Not Found” message. Why? The trailing slash is not handled. This shows how you can’t take too much for granted with Node, at least out of the box. If you want it, you pretty much have to build it. Usually you will be using a framework of some sort (like Express) that automatically handles a lot of that low-level stuff for you. As an example, let’s handle that trailing slash issue. There’s lots of ways but here’s a relatively simple way to just change the switch parameter:

Here the switch statement now uses a replace transformation on the url parameter of the request object to remove any trailing slashes. Notice there that this also let me change my “Home” or root route to not have a slash at all. So basically a route that just is the domain (localhost:3000) will resolve correctly.

As an example of one more thing that won’t work correctly, try this:

That’s a URL with a query string, which is basically just a series of key-value pairs. That will return a 404 or “Not Found” response. The trick is being able to determine what those parameters are. Since query strings are a common use case in URL handling and routing, I’ll cover just a bit about this here. First, you can get the query string for any passed in URL by adding the following:

Here I use the url module which allows me to call a parse method on the url and return any elements of that URL that are a query string. In this case, I just log the values to the console so you can see that they are actually being grabbed. However, none of this does anything for my URL routing. Any URL that still has a key value pair will be marked as 404 “Not Found”. So what I did was update my replace transformation to filter out the query string. Now try this URL again:

Now that should take you to the “About Page”.

When I say “take you to the page”, what I really mean is that the logic will send the plain text “About Page” to the browser. There is no actual page you are being taken to. And that brings up a good point about this particular example: it’s a very poor version of routing. That said, routing, as a mechanism, is simply any functionality that acts to serve content to a client when that particular content is requested. For web-based client/server applications, the client specifies the desired content in the URL. A server parses that URL to determine the information it needs.

Serving Actual HTML

Now let’s change this example to serve actual HTML pages. What we’re going to want to do here is serve up a static resource, which is what an HTML page is. Create a directory called public and within that directory create a file called index.html. In that file put this:

Then change testServer.js as such:

Now try to go to http://localhost:3000. You’ll see that the browser displays the markup of the page. Note that the markup isn’t processed as HTML because I’m still sending everything as plain text — but at least now I’m reading a file rathern than just sending strings around.

Regarding how the code works, you already known about the filesystem (fs) from the first post in this series. My logic here calls fs.readFile() to read the contents of the specified file. The “__dirname” will resolve to the directory that the executing script resides in. I then simply add the path to the particular static resource I want the route to use: “/public/index.html”. The fs.readFile() executes the callback function when the file has been read. If the file is read without a problem, the contents of the file are passed into data. You’ll note that the value of data is what I pass to the res.end() method.

But what if the file didn’t exist? Or if there were permissions issues regarding the file? In that case, the err variable would be set. Right now, however, nothing would happen. For example, trying changing line 7 in the code example to reference “/public/testing.html” or some other non-existent page. You’ll find that nothing at all is returned by the server. So I should handle the error situation. That can be done by using the following code:

In this case the function returns an HTTP status code of 500 indicating a server error and contents to that effect are sent as part of the response. As before, if the file is read successfully, the file is sent to the client with the specified response code and content type. You can test this by, again, changing the name of the index.html above to some non-existent filename.

So this seems to work pretty well. However, I’m still serving plain text rather than HTML. I bet you can see how to fix that, right? Just change the above to the following:

You should now see the markup is actually rendered by the browser since I’m saying that the response body is “text/html”. I keep the “Internal Error” response as “text/plain” since, after all, I’m not serving anything other than plain text in that case.

Modularize the Functionality

This logic seems to work and clearly I could do something similar with each of the other cases in my switch statement … but then I’m going to be repeating code all over the place. It would be better to come up with standard way of using the routes. How about this?

In each case statement, you can see I’m indicating the need here for a helper function, serveResource(), that’s doing the bulk of the work. So let’s create the function serveResource(), essentially copying the previous file system code into it. Here’s an example (which can go under your require statements but before the createServer() logic):

There are a few few differences in this logic. In the second line I replace the hard-coded path portion with a variable. In the seventh line, I remove the hard-coded content type and instead use whatever was passed in. In terms of the logic, the function itself is still simple: it takes in the path, the content type and the response code.

That’s it. You’ll see that http://localhost:3000 still serves up the index.html page and renders it as HTML. To get the other pages to work, you would have to add them to the public directory. These HTML pages are static resources, as mentioned before. You are not limited to HTML pages, however. Let’s serve up an image as well. First create an img directory in your public directory. Then grab some image file and put it in there. Finally, update the routes as such:

Now you can check that with http://localhost:3000/img/cthulhu.jpg. Obviously you should use a URL that matches whatever grab whatever image you want and put it in your public/img directory.

If you’re mostly going to serve HTML and also expect a 200 response, you could make the serveResource() method account for that as defaults:

Then the routes clean up a little bit:

And with that you have a very rudimentary, but working, application that acts as a web server. The fact that I can create something so simple is a testament to Node.js. That said, what I created here is very minimal and clearly would not be robust as a scalable solution. When you actually want to create something useful, you’ll most likely use one of the many frameworks that are available. In the next post, I’ll take a look at one such framework, called Express.

Share

This article was written by Jeff Nyman

Anything I put here is an approximation of the truth. You're getting a particular view of myself ... and it's the view I'm choosing to present to you. If you've never met me before in person, please realize I'm not the same in person as I am in writing. That's because I can only put part of myself down into words. If you have met me before in person then I'd ask you to consider that the view you've formed that way and the view you come to by reading what I say here may, in fact, both be true. I'd advise that you not automatically discard either viewpoint when they conflict or accept either as truth when they agree.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.