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:
- Users can access the application from a web browser
- The user should see a welcome page when requesting
http://domain/start
which displays a file upload form - 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) -> require("./actions/#{name}")[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 else 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