Fetching lunch with Node.js + Coffee Script + PhantomJS + Express + Zappa + MongoDB + Handlebars

So I've been working on a little side project for laughs and giggles, which scrapes the lunch menus for restaurants in the local area. Doing my impression of a nerdy magpie, I chose the shiniest tools I could find at my disposal. You can see the site in action at http://lunch.webshaped.net, but if you're not in Helsinki you won't see much if you tell the site your location as it restricts restaurants within 2km.


I chose to give Node a try because there has just been so much hype about it that it seemed like it deserved a bit of attention. So far I've been very impressed. The first thing you notice is the sheer speed of everything: from the package manager npm to executing code, it makes the ruby equivalents feel distinctly stone-age. Installing gems with ruby gems often requires a compulsory tea break, but with npm you haven't really got time to get your ass out of the chair.

The same can be said for running code: it's true that it's a bit unfair on Ruby using the rails runner as a comparison, as it loads the whole Rails stack, but the truth is if you're coding Ruby you're probably coding RoR so you're normally forced to load Rails to do anything. And lets just hope you're not using JRuby: then you can get started on another cup of tea just to run a single RSpec spec.

Coffee Script

I have to admit I was rather wary of Coffee Script when I first came into contact with it. It somehow struck me as a bit weird and unnecessary: I've been doing this Javascript malarky for years, you can't just go taking away my braces and semi colons just like that! The clincher was when I was investigating what javascript library I could use to simplify coding classes: nothing out there looked that great until I noticed that Coffee Script had that baked right in. The only small wrinkle is the Java-style object creation syntax ("new Horse() vs Horse.new()"), but in general I've been blown away with the Coffee Script support for classes and inheritance: it's way nicer than any Javascript library I've used up until now such as Ember's. You've got Ruby style instance variables, class methods, dead easy constructor syntax, it's all pretty sweet.

The most amazing thing I've noticed whilst coding in Coffee Script is how much more efficient it makes your coding, and how many stupid bugs you can avoid. The fat arrow is genius, how often do you have to debug problems where 'this' isn't what you thought it was? Gone is the lame Javascript idiom such as something like the following inside a function:

var self = this;
buySomeHorseMeat(function() {
   self.iLikeHorse = true;

Instead we can just write:

buySomeHorseMeat =>
  @iLikeHorse = true

Express + Zappa

For the web framework I wasn't quite sure what to choose: Express seemed minimal and clean, but Tower.js was quite tempting with all its Rails-esque abilities. A few things put me off Tower though: the stable branch seemed to have a bug meaning the dev server didn't load, which didn't give such a good first impression; the documentation was annoyingly redundant (many sections are the same docs copy pasted verbatim); and when I created a test app it was so incredibly obese. The lunch app is pretty simple so I didn't really need all that crap.

In the end I alighted on Zappa which is basically a thin Coffee Script layer on top of Express. The original author, Maurice Machado, who seems to be a bearded Brazilian fellow, has by all accounts gone off round the world travelling in 2011 and never returned, but thankfully someone else has taken over the project. Let's hope he is relaxing in an exotic location and not languishing in prison in some disagreeable totalitarian regime.

Using Zappa my web server code looks like this, including Handlebars template handling:

{Restaurant, Menu} = require './db'
hbs = require 'express-hbs'

require('zappajs') ->
    @use 'static'
    @app.engine 'hbs', hbs.express3
        partialsDir: __dirname + '/views/partials'
        defaultLayout: __dirname + '/views/layout/default.hbs'
    @set 'view engine': 'hbs'
    @get '/': ->
        Menu.findTodays (err, objs) =>
            @render 'index',
                title: 'Whats for lunch?'
                today: Date.todayPretty()
                menus: objs
    @get '/nearby': ->
            (err, objs) =>
                @render 'nearby',
                    title: 'Whats for lunch today?'
                    today: Date.todayPretty()
              menus: objs


I went with Handlebars because I've dabbled with template libraries in the past that use a whole new DSL instead of HTML to express markup, and it ended in tears. Maybe I'll be converted at a later date to something like Jade, but for now I quite like writing html that looks exactly the same in the browser as it does in my text editor thank you very much. I don't want to go learning a whole new syntax just to build some markup, I think HTML is more than elegant enough. My Handlebars template for generating the menus looks like this:

<h2>Lunch menus for {{today}}</h2>

{{#unless menus}}
    <p>Hmm, we don't have any menus today. Can it be the weekend?</p>

{{#each menus}}
        <h3>{{restaurantName}} {{#if distance}}<span class="distance">// {{formatDistance distance}}</span>{{/if}}</h3>
        <div class="menu">
        {{#each items}}
            <div class="menu-item">
                <div class="item-name">{{name}}</div>
                {{#if price}}
                    <span class="item-price">{{formatPrice price}}</span>
                {{#if description}}
                    <div class="item-description">({{description}})</div>

MongoDB + Mongoose

Finally I chose MongoDB and Mongoose for persistence because Mongoose seemed a nice library to work with and it seemed really easy to get up and running with MongoDB. I like the way you don't need to create a database or make migrations when you change your schema: you just connect to the DB and Mongo will create it, and if you change the schema at any time Mongo will just deal with it. Nice. As a bonus MongoDB has support for geospatial queries right out of the box, so I was able to add that to my app with just a few lines of code.