April 3, 2012

The Node Beginner's Example in CoffeeScript

I started programming in the era when to play a game, you had to type it in by hand. To this day, I still learn best by following along with an extended tutorial; getting it to work initially and then playing around with it to see what happens.

I was delighted to find Manuel Kiessling's The Node Beginner's Book. What a little gem of a tutorial. It starts from the obligatory node http application, and then extends it to deal with real world problems like asynchronous handlers, listening to events, organizing the code, and more; without going too deep. As he states, it's designed to be the tutorial he wishes he had when he first started, and you'll go from beginner to advanced beginner.

I decided to follow along in CoffeeScript. The tutorial provided a great little workbench to try out all sorts of things from code organization to putting the project up on GitHub to trying out code documentation in docco.

I'll walk you through some of the code, but check out the docco documentation for the full details. Like the author did, there's more code, and more abstraction than is required. The point of the walk through is to have a place to try some things out that might come in a "real" application.

Here is the basic specification from The Node Beginner Book:

  1. Users can access the application from a web browser
  2. The user should see a welcome page when requesting http://domain/start which displays a file upload form
  3. By choosing an image file to up to upload and submitting the form, this image should be uploaded to http://domain/upload, where it is displayed once the upload is finished

Here's what you get if you type:

coffee begin.coffee

...and browse to http://localhost:8989.

Beauty, right? Hey, it's not a design project :)

A convention in node is to name the entry point of the application index. I'm not a fan of that convention. Index to me is a view name. My personal convention is to start the application in a module with the same name as the application itself. So the entry point to my application is called begin.

# The entry point to the application.

server = require './server'

server.start 8989

The server module is my code. It simply starts the canonical node HTTP server and starts listening on the passed port. It provides a handler that parses the incoming request URL, bundles them up into an object, and passes them into a router function.

The router is a simplistic route map. The little action function serves as a macro to return the actual function (functions are first-class citizens remember) that is exported inside the module referenced by name. This is a little trick of mine to put all the "actions" in their own folder. It works as long as the action exports its namesake. This shortens the map (routes). The single exported route function, looks up the passed URL path in the routes object literal (or sets the next action to the not_found handler) and processes the request.

# Router determines what to do with the requested path

action = (name) ->

routes = 
  "/": action('start')
  "/start": action('start')
  "/show/uploaded": action('show')
  "/show/sad_panda": action('show')
  "/upload": action('upload')

exports.route = (http) ->
  console.log "Routing #{http.path}"
  process = routes[http.path] ? action('not_found')
  process http

Again for the purpose of code organization I separated out the "views" into their own folder. Some of the simpler actions like start just write an HTML response.

# The start action

html = require '../lib/html'
view = require '../views/start_view'

exports.start = (http) ->
  console.log 'Start action invoked'
  http.respond = html.write_html
  http.respond view.render()

Notice how on line 8 (above), I attach the html.write_html to the http object. This is me noodling with the this property in JavaScript/CoffeeScript. It's different than the meaning of this in C#. The reference to this refers to the context of the function when it is called. It has nothing to do (necessarily) with an object instance of a class. So when you set the respond property to the function html.write_html you essentially spot weld a method on the instance that is held by the http parameter. Invoking it on line 9 (above) with http.render makes the @response properties of http available as on lines 8 and 9 below. Keep in mind that http is just an ordinary object (not a type from a class). The @response was attached to the object way back in the server.

exports.write_html = (html = '', status = 200) ->
  console.log 'writing html'
  headers = 
    "Content-Type" : "text/html"
    "Content-Length" : html.length

  @response.writeHead status, headers
  @response.end html

All that was me trying (probably to an extreme) trying to escape from the Kingdom of Nouns as referenced in the book.

One other discovery worth mentioning is handling asynchronous programming (without an async or await keyword). Take the show action; responsible for determining what image file to write.

# The show action displays an impage

html = require '../lib/html'
view = require '../views/show_error_view'
fs = require 'fs'

exports.show = (http) ->
  match = http.path.match /\/(\w+)$/
  http.image_name = if match then match[1] else 'sad_panda'
  console.log "Display the image #{http.image_name}"
  fs.readFile "./images/#{http.image_name}.png", 'binary', file_loaded(http)

file_loaded = (http) ->
  (error, file) ->
    if not error
      http.respond = html.write_png
      http.respond file
      http.respond = html.write_html
      http.respond view.render({image_name:http.image_name, error}), 500

The first lines just determine what image to display, in a rudimentary way. The last line of the show method calls node's readFile method. The last parameter is the callback to invoke when readFile is ready. I could have defined the function anonymously, and in this particular case it might not matter. Instead, something I really like is invoking a function that returns the actual callback.

If you look closely file_loaded is actually being invoked immediately. However, the result of the function is another function (it returns a function) that will serve as real callback later. The function file_loaded is also saving the state of the http object in a closure. This pattern also has the effect of avoiding deeper and deeper indentation across callbacks. The functions and the "steps" are all along the left side of the code file (but still very much asynchronous). I thought this was an easy way to understand what has happening.

Please checkout the GitHub page for a line-by-line account of the source. Please mess around with the code on your own, and check out The Node Beginner's Book.

What's Next

Now that I've leveled up to (Level 2) as a Node advanced beginner I realize just how far I have to go.

  • Exploring some of the bigger frameworks like Express/Connect and Zappa.
  • Doing some of the more mundane things
    • Authorization
    • Logging
    • Persistence

Node + CoffeeScript is Love.

No comments:

Post a Comment