Top 6 Gotchas with Node.JS

Node.js has many advantages. Many developers know Javascript at least a little bit. Node offers great performance for applications that wait on I/O (like web services). There’s a vibrant open source community creating reusable node modules for almost every task (and a handy package manager to bundle them together). Node is cross platform – the same code runs on Windows, OSX and Linux. And of course, there’s the promise of re-using Javascript patterns on both the client side and the server side.

I love node, but I’ve run into several gotchas with node in production the last few years. Here are the top 6.

1. The website (server process) is fragile. With node, the process restarts on every unhandled exception. This is different from Ruby on Rails, where you have a separate server process like Unicorn, or PHP where it runs as an Apache module.

With one client that was transitioning from PHP to node, our first deployments felt like we were “plugging the dike” – there were so many server crashes. Until we stabilized things, this sort of gave node a bad name (the perception was that PHP was more stable – but in fact the exceptions were just hidden).

Handling all potential errors, as well as unplanned exceptions is impossible. Most deployments have another process to restart the primary node server process when it bombs. If you’re using a PAAS provider, this will be provided for you, but if you want fine grain control of your server (most web application companies will), hosting and scaling (keeping processes up and enough process around) is a challenge.

Common server-restarting libraries for node are: Upstart, Forever, or God.

2. Writing and maintaining asynchronous code. Callbacks are a simple mechanism starting out, but as your server code grows in complexity, they become a hairball. Crazy callback chains (AKA “callback hell”) make code unreadable. Even simple code that could’ve been procedural is forced to use callbacks due to the libraries you’re using.

Mixing synchronous and asynchronous code makes it difficult to handle every error. Furthermore, you have to understand the event loop to know when execution actually switches contexts. As you’re running your code – if you forget to call a callback, you’ll be left waiting to find out what happened. And, asynchronicity makes even your tests become more complicated.

The Async library exists to handle common asynchronous patterns. Personally, I prefer the “promise” (or “deferred”) pattern. It seems like a hack to send a function as a parameter, instead the promise pattern returns an object representing the state of the asynchronous call instead. jQuery has a promise library for the client side. When is the promise library we selected for a recent node-based web site (comprehensive API and more standards based).

3. Too much choice.  The flip side of all those great NPM packages is that there is no clear winning web framework (think Ruby on Rails) for node – in fact, there are several competing server side frameworks (express, hapi, sails, kraken). Each with their own conventions for:

a) File structure
b) Deploying code
c) Building assets
d) Automated testing
e) ORM

And of course, the best choices for each of these changes monthly. This, combined with #2 is why Node is not yet a great choice for simple human facing web sites.

Of course, it’s not necessary to use node on the server.  On one team we used it to great success for building thousands of lines of Javascript and templates to be hosted inside a Python Django website. The problem of too much choice exists for client side frameworks (and build tools) as for the server side (you’ve heard of Backbone, Angular, Ember). Node (with the help of tools like grunt or gulp) is essential to building a modern front-end, just prepared for the evaluation time and conflicting opinions on frameworks.

Screen Shot 2014-12-04 at 10.55.39 AM

Visualization of NPM Packages

 

4. Javascript the language. Many developers think they “know” javascript from having used it in client side code. In fact, they haven’t learned about things like prototypal inheritance, function binding (what is this pointing to?), or variable scoping that are unique to Javascript. Even once you’ve chosen Javascript, there are still a lot of decisions to be hammered out amongst your team. Code conventions (4 spaces or two?) Camelcase or snake case? Jshint is a huge help with this. But, be ready for…

5. Functional versus object oriented programming. There are two very distinct paths any Javascript project can take. Functional style favors dependency injection for testability and clear interfaces (that are theoretically more re-usable). Object oriented favors composition and inheritance for more readable code. The best choice is probably a mixture, as both are silly at the extremes. My current team is finding compromises, but please let me know if you’ve got any guidelines for when to be functional and when to be OO.

6. What about Coffeescript? I hate semicolons. Compared to other languages that give white space meaning (Python and Ruby), Javascript feels heavy and hard to read. I love Coffeescript because it lets you write clear (information dense) code while avoiding common Javascript pitfalls (see #4 above). Node supports Coffeescript natively, but unfortunately, transpiling from Coffeescript means you lose a debugger (and sometimes line numbers).

You also lose the benefit of going with a “lowest common denominator” language (everyone knows Javascript, C, or Java). And, you still need to know javascript deeply (#4). Worse, it can create division on your team. Make sure if you switch to coffeescript, you switch all the way.

Thanks to Scott Nonnenberg, author of thehelp-cluster (a solution to #1) for feedback on this post.

Please leave a comment and tell me: what problems are you having with Node.js?