Converting a Rails app to Node.js

This is going to be a fairly long post detailing the process I recently went through converting a Rails app to Node.js.

I recently joined a new startup as CTO/Lead. They had paid for an MVP of their product which was written using Rails.

The rails app used many pieces of well known Rails and Ruby infrastructure, including:

  • Capistrano for deployment
  • Sidekiq for background tasks
  • Thin + Rails + Unicorn for runtime
  • CoffeeScript with Angular.JS for client side
  • SASS for stylesheets
  • Compass for SASS/CSS add-ons
  • HAML for server side templates
  • MySQL and MongoDB for storage
  • Many other Gems, many of which I didn’t (and some I still don’t) know their purpose


Now many people are going to ask “why convert it?” at this stage. This was a fairly complete app, and it mostly worked, but I ripped it apart and replaced it almost wholesale. I thought long and hard about whether I should do this or not, so here were my reasons:

  • I don’t know Ruby well, and know the Rails ecosystem even less well (whereas I know Node.js well).
  • Hiring – I’ve had good experiences in previous roles finding frontend JavaScript programmers who can be tasked with backend work too. It’s getting harder to find good Rails people these days, and they have to skip over the language “barrier” to do UI work (and often choose to do UI work in CoffeeScript for this reason).
  • Magic – Rails (and partly Ruby) is full of “magic” – lots of things just magically happen, and if you don’t understand the Rails system really well (such as the “asset pipeline”) then you are quickly lost. Node.js is explicit in everything it does – you may have to write a bit more code – but you know where and why something happened.
  • Performance – just starting the Rails app on a fast machine took a minute (which can make development painful), and the Ruby runtime is orders of magnitude slower than Node’s V8 runtime, so much so that the app now feels noticeably faster.
  • Ability to add and change things – if I kept the app in Rails it would hinder my ability to add and modify code.
  • Queryability – the use of ActiveRecord for everything meant that they only way to ask the system questions was via ActiveRecord. Using the DB directly (especially with data split across MySQL and MongoDB) was impossible due to much functionality implemented in the Ruby side of the model.
  • PostgreSQL – I’m a postgres guy – I don’t trust MySQL.
  • Referential integrity – Rails punts (by default) on referential integrity in the database. This was a serious WTF for me.
  • Porting time – we were pre-alpha still, with no customers, so porting now rather than later was a smart choice allowing us to move forwards faster.

That’s most of the reasons. I’m sure there were more, but you get the idea.

The rails app was ~6000 lines of Ruby, 4000 lines of SASS, 1500 lines of HAML, and 2500 lines of coffeescript (which remained unchanged).

I estimated 6 weeks for figuring everything out and porting it over. In the end I finished in around 3 to 4 weeks. Most of that time was figuring out how Rails did things. I learned a lot about Rails in the process, and like some of what I see – it definitely does a lot for you. But doing the same level of work in Node didn’t take much, and I’m glad I went through the process.

Here’s the setup I ended up with today:

  • Express server mostly stock aside from connect-memcached for sessions
  • PostgreSQL as the only database – database schema constructed by hand written SQL
  • Jade for server side templates
  • SCSS via node-sass for stylesheets with Compass working
  • Front end code mostly untouched (CoffeeScript and Angular.js) – will probably convert soon to pure JS
  • Deploy_to_runit for deployment: https://github.com/baudehlo/deploy_to_runit

Here’s the directory structure I use for Node+Express projects:

lib       - general libraries
  model   - database accessors
public    - files that get served to the front end
  assets  - files that are considered part of the app
routes    - files that provide express routes
  api/v1  - files providing REST API endpoints
views     - Jade templates

Delivering Stylesheets and JavaScript

Both SASS/SCSS and CoffeeScript need to be converted server side to be interpreted correctly by browsers (OK not strictly true, but it’s most compatible that way). Unfortunately the modules for Node.js don’t support the original “SASS” syntax, which is indent based, but they do support SCSS which is a one-step conversion.

The original SASS files were converted to SCSS using Ruby’s SASS module which comes with sass-convert to do the conversion for you – a simple matter of a shell script to convert every file to SCSS format. Despite the name node-sass cannot process SASS files. Thankfully I prefer the look of SCSS (the syntax is closer to CSS).

In order to emulate the Rails asset pipeline I convert the SCSS and take the SHA256 sum, and provide a link that is unique as until the CSS changes, this allows you to provide cache headers and yet always deliver up to date CSS.

Here’s most of the code for compiling the SCSS and serving it:

var css = sass.renderSync({
    file: __dirname + '/public/assets/stylesheets/application.scss',
    includePaths: [__dirname + '/public/assets/stylesheets'],

var shasum = crypto.createHash('sha256');
var css_sha = shasum.digest('hex');

app.get('*', function (req, res, next) {
    res.locals.stylesheet_sha = css_sha;
app.get('/assets/stylesheets/application-' + css_sha + '.css', function (req, res) {
    res.setHeader('Cache-Control', 'public, max-age=31557600');

Then in your template you can use the stylesheet_sha variable to include you stylesheet. The final version of this code actually uses file watching in dev mode to recompile the SCSS when the stylesheets change, allowing me to dynamically develop with the server running. I use the one-file CSS delivery even in development mode because unlike JavaScript you rarely need to see the source of the CSS with modern debug tools.

Adding in Compass was a rather more complicated affair. If you google for “compass + node.js” you come across things like “node-compass” which uses Ruby on each request to serve up the compass files. Not what I wanted – I wanted to remove the dependency on Ruby altogether. After much hunting I discovered that Compass was mostly a bunch of SCSS files. Some places said that there was some Ruby code that ran too, but I wanted to see how far I could get just using them as straight SCSS files. So I imported the files (from gems/compass-0.12.2/frameworks/compass/stylesheets/compass) into my project, and magically it all seemed to Just Work ™. There are probably edge cases of Compass I’m not using which are broken, but for now this is good enough.

Next coffeescript:

    src: __dirname + '/public',
    compress: false,

That was easy. In production I do something similar to the CSS code – compiling it statically all into one file, minimizing it using uglify-js, and provide it using a SHA sum link.


Now I had the basics served up I needed to work on routes (and soon, views).

I have a standard way of serving up all routes. In my main app.js file I have:

module.exports = app;
find.fileSync(/\.js$/, __dirname + '/routes').forEach(function (route_file) {

That allows nested folders under the “routes” folder, and loads every file in there. Then each file can get at the app via:

var app = module.parent.exports;

My routes are logically separated into separate files relevant to each part of the application.


I don’t use ORMs. I think they shield you too much from the guts of what is happening at the SQL level, and they tend to provide a layer of abstraction too deep and hard to debug when they go wrong.

But writing model APIs is simple enough, and much can still be abstracted away into a separate library, such that a typical model file looks like this:

"use strict";

var db = require('./db');

exports.get = function get (id, cb) {
    db.get_one_row("SELECT * FROM Answers WHERE id=$1", [id], cb);

exports.get_by_submission_id = function get_by_submission_id (submission_id, cb) {
    db.query("SELECT * FROM Answers WHERE submission_id=$1 ORDER BY question_id", [submission_id], cb);

exports.count_by_submission_id = function count_by_submission_id (submission_id, cb) {
    db.get_one_row("SELECT count(*) as num_answers FROM Answers WHERE submission_id=$1", [submission_id], cb);

exports.upsert = function upsert (submission_id, question_id, answer, cb) {
    db.query("INSERT INTO Answers (submission_id, question_id, value)\
        SELECT $1,$2,$3 WHERE NOT EXISTS \
        (SELECT 1 FROM Answers WHERE submission_id=$1 AND question_id=$2)\
        RETURNING id", [submission_id, question_id, answer],
    function (err, rows) {
        if (err) return cb(err);
        if (rows.length && rows[0].id) {
            return cb();
        db.query("UPDATE Answers SET value=$1 WHERE submission_id=$2 AND question_id=$3", [answer, submission_id, question_id], cb);

Now there’s a lot I’m not saying here – creating all the routes and models appropriately was a large part of the work in converting the app to Node. Mostly I relied on Chrome’s web inspector to see what the Ruby version sent over the wire for each endpoint (JSON in most cases), and creating the same data feed in Node. This was a time consuming process, but valuable because it taught me about the entire structure of the application, and also helped me fix some of the weaknesses in the data model.

Converting HAML to Jade

This was the next biggest part of this project. Jade is very similar to HAML in style, but slightly cleaner in syntax. It is slightly less flexible in some things (e.g. no dynamic includes), but overall I came out of it knowing a lot about Jade and even created the #jadejs IRC channel to help other people.

After a number of hand conversions I came up with a set of regexp substitutions I applied to each file (I did this in SublimeText, but perl or sed or just about anything would work):

Fix tags:
Search: ^(\s*)\%
Replace: $1

Fix attribute wrappers: tag{foo: bar} becomes tag(foo: bar)
Search: (\w)\{(.*)\}
Replace: $1($2)

Fix attribute “key: ‘value'” to “key=’value'”
Search: (\w):\s*([“‘])
Replace: $1=$2

The remainder of the contents I converted carefully by hand.

One thing which was a constant irritation was the use of nested values in attributes due to using AngularJS, I’d see a lot of: {ng:{show: 'some_condition'}} which translates to: (ng-show='some_condition'). I much prefer the Jade representation of this because it looks like HTML attributes, rather than JS or Ruby code.

The next thing I discovered which caused problems was helpers. Rails provides a bunch of default helpers for forms, input fields, etc. I decided to use Jade’s powerful mixin feature for this. So Rails’ = f.text_field :email became +form_text_field('email', data.user.email) – slightly more complex, but clearer where the default value comes from. The code for this mixin itself (which I keep in a separate file) is:

mixin form_text_field(name, default)
    input(id=name, name=name, size=30, type="text", value=default)

A more complex mixin can be created for select fields:

mixin form_select_field(name, object, include_blank)
    select(id=name, name=name)
        if include_blank
        if (Array.isArray(object))
            each val in object
                option(value=val)= val
            each val, key in object
                option(value=key)= val

Mixins can also have sub-blocks so the following works:

mixin form(action)
    form(accept-charset="UTF-8", action=action, method="POST")
        input(name="utf8" type="hidden" value="✓")

    form_text_field('name', data.name)

One complexity I found was that HAML allows for dynamic includes, which Jade doesn’t. This allows HAML to implement something like:

= popup 'roles/new'

Which behind the scenes will load the template in views/roles/new and include it in the block of the popup.

Whereas in Jade you have to use two lines as follows:

    include ../roles/new

Not too much of a hassle, and once again makes it explicit about what is happening.

Next Steps

The main thing I left as-is was the front end code, written in CoffeeScript. At some point I may convert this to plain Javascript, which I just find easier to understand as it’s now the same language as the backend, though CS obviously brings with it some nice shortened versions of the verbose JavaScript way of doing things.

This blog was also turned into a presentation for the Toronto Node.js user group. You can view the slides below:


6 thoughts on “Converting a Rails app to Node.js

    • The original app had no tests. They are on my roadmap to build, though right now we’re focussed on purely doing manual integration tests. There are lots of options for testing within Node.js.

  1. Andreas says:

    Thank you for sharing this information. I’m a part-time Rails developer for over 10 years and I love to read it.
    Best, Andreas

  2. raelgc says:

    I started to read your article, then hit the “I don’t know Rails well”. Probably this is the reason why some pointless reasons like:

    – Magic: yes, if you don’t know a language and framework well, a lot of stuff will appear like magic.
    – Ability to add and change things: yeap, hard when you don’t know the technology you’re using.
    – Queryability: You can query directly to the DBMS if you want.
    – PostgreSQL: it’s the preferred DBMS in Rails world and well support.
    – Referential integrity: you can use that in Rails easily. In my current job, it’s mandatory.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s