Something just doesn’t feel right about node.js.
After coding in it for almost a year, it’s been fun, but I’ve decided it’s just a waypoint to somewhere better.
But I can write programs for the network really easily!
There is no doubt that node.js got some things right. Some while ago I wrote a very high-performance Syslog Collector that could process and parse 250,000 log messages a second. It could do this with thousands of active simultaneous TCP connections. It also supported UDP, HTTP, and SSL. It was written in C++ and used the Boost ASIO framework. It almost never crashed, and didn’t leak memory. But it took 5 months to write (and then re-write), test, and deploy. So learning how to write similar programs in node was a real breakthrough when it came to speed-of-development.
Need a HTTP server? require the http module. Ditto the net module to bring up a socket server. And node is an event-driven framework, promising performance superior to the old way of a thread-per-connection model. node.js became popular in a way Twisted never seemed to.
What’s wrong with node.js?
Or how about this, using the node.js REPL?
>  + 
Why doesn’t 1 empty array plus another empty array equal an empty array? It does in Python.
>  +  * 5
Everywhere I look node.js developers are debugging programs in a really primitive manner, and constantly express frustration with the tools available to them. Tracking down why exceptions are thrown in callbacks, and in unit test frameworks like Mocha and Chai, is often a nightmare. Hours are spent trying to work out why programs are failing. Liberal use of console.log() seems to be the only tool most developers even use, when it comes to debugging issues.
Most programmers, within 36-hours of first programming in node, discover callback hell. Entire websites have been dedicated to explaining how to avoid this problem. It’s deeply ironic that the asynchronous, event-driven nature of node, which gives it so much performance advantage, results in such unsightly and shaky code.
But we’ve got Promises, you say! I agree. If it wasn’t for modules like Bluebird I think most node programmers would have given up by now. I really like working with Promises — it introduces an aesthetic back into the code. Chaining a sequence of Promises is a subtle way to program a computer and a consciousness-raising exercise. In fact Promises are so much fun that it makes you forget that they only exist to fix the really, really bad problem of callback hell — a problem which, I suspect, seriously threatened the viability of writing serious programs in node.
I’ll take one core please, but just one
node.js always runs in a single thread. This makes it much easier to deal with data that is shared across functions and modules. It doesn’t mean there are no race conditions, but it does mean you can be sure that your code won’t be interrupted while modifying an object. But once your node program maxes out a single CPU core, where can you go? You can’t make use of all the extra processing power that exists on multi-core machines. You’re stuck. Sure, there is Cluster Node, but it’s experimental and significant chunks of your program may need a re-write to use it. And your overall program design may never have considered that you may need more than one process on the same machine to cope with the load.
Where to go next?
Writing high-performance programs for the network and Distributed Systems has now become a extremely important element of contemporary programming. And frameworks which minimize context switching — like node does — allow us to build high-performance servers. But better alternatives to node.js exist.
An obvious candidate is Go. It comes with a robust standard library, built for implementing programs for the network, so makes coding servers as easy as node. Goroutines allow the programmer to forget about callbacks and code in the way you, the programmer, naturally think — in a linear manner. And underneath, Goroutines are really lightweight so the cost of the context switch is much less than an OS thread — and it was the OS thread context switch that node.js was built to avoid.
Go can also take advantage of multiple cores without too much trouble. It’s built into the language. I’ve written a couple of significant programs in Go, and it’s been such a better experience than writing node code.
Don’t take my word for it
28 thoughts on “Is node.js just a stopgap?”
yo dawg – i heard you like c++ and promises –
…this seems destined to outgrow that repo.
Thanks Mr. G — the serialization project itself is pretty interesting too.
I don’t agree. Most of your concerns have or can been solved through external libraries or better code design.
Debugging – Check out node-inspector for debugging. Agree that it’s annoying to have to launch it in browser.
Callback hell – solved by async
One core – This just means you should develop your node processes to be stateless. Rely on intermediary caches or the db. Cluster module does kinda suck, but PM2 is a pretty good tool for launching/controlling/managing node.js procsses. Actually saw that you were @ Riverbed. I was solving this exact issue there (before I left).
Also you can check out my website, backend built 100% in node. http://www.dockumo.com
Thanks for the feedback, codeandcodes.
As for debugging, I know about node-inspector, but no-one writing backend code ever seems to use it. It may the right thing to use but my point is no-one seems to do it. That tells me it’s a poor tool for the job.
Hi Phillip, in addition to what was said in that thread and in a few others, let me add my 2 cents:
2) Debugging – like it was said node-webkit is very useful, you can make it break on exception. Also JetBrains products have awesome debuggers (also check spy.js – tracer).
3) Callback hell – like it was mentioned take a look at ES6 generators and at following module at particular – https://github.com/tj/co. Note how you can do global try-catch for multiple async calls!
4) Yes you need to provide architecture for programs with multiple cores support from the very beginning (with Java it is much simpler). But hey, there was a guy from IBM, talking that if you scale to multiple machines, then that is where even in Java you’ll have to rewrite your application code. His point was that with Node you would think about it from the very beginning. My word – for experienced developer it is not an issue, for everyone else that is a way to learn how things are working.
Hi Sergey — thanks.
Adding an array to array is something I do quite a bit in Python, but that wasn’t the point I was trying to make. The consistency and coherency of a language is often measured at its edges, not in the core. The examples I outlined above seemed very strange to me, and I couldn’t reason about them. It makes one wonder if those behaviours just “fell out” of the language, or followed some explicit design principles.
I’ve got a feeling that node.js is going to become even more ubiquitous, if anything.
Callback Hell: As you discovered, Promises are already today a great way forward. ES6 generators seem to opening up the path to C#-style async/await keyword-style programming to build Promises. Plus there are interesting things going on in the Reactive Programming world (libraries like Rx.js).
Debugging: This surprises a lot of people, but the Node Tools for Visual Studio makes VS a killer IDE for Node development (and JS development in general). https://nodejstools.codeplex.com/ (Sure it is Visual Studio and not everyone loves VS, but it proves that Node.js debugging can be done and done well.)
One Core: This reminds me of Python’s Global Interpreter Lock. There’s still not quite a good solution there either. One of these days we’ll all figure out what async should mean in our programming languages.
Max — thanks.
Saying I should use an IDE to fix my debugging issues won’t work for me. 🙂 Forking out money to Microsoft won’t cut it for many people, not least of which is that we don’t run Windows.
Promises were not designed to manage deeply nested callback chains — though they do this. Promises are a way to model problems using functional thinking, declarative modeling vs imperative modeling. This should be clear from the “words” used in the api itself. Promises allow you to comprehend flow in terms of the interactions between values. As values change (asynchronously) the logical “chain” you’ve modeled (declared to exist) is perfected.
These people are driving the Node community. Node opened up systems programming to these people — people with a deeply felt, natural understanding of asynchronous programming, evented concurrency modeling, closures, functional thinking, and more (such as the “full stack” knowledge of UI/UX, programming with services, etc. that those who built web-based applications exclusively possess). IOW, the concepts that schools don’t prioritize and modern software development does.
So the comparison with Go is false (though Go is an absolutely fantastic language). JS has a history and social influence, and the reality of its default integration with the browser (as well as many databases, UI kits, and so on) cannot be dismissed. JS is not yet another concurrency modeling language, and its integration with Node creates something that is greater than a sum of parts.
Sandro — thanks.
I don’t follow why the comparison with Go is false, at least within the scope of this discussion. node.js and Go are both languages with which I’ve found it easy to write programs for the network — both come with a standard library specifically for those types of programs. It just seems to me Go also promotes a more sound programming style, which is something I value highly.
Hey philip! Go let’s go! 🙂
Ha! Thanks Bob. 🙂
Maybe one day something else will take its place, and I know theres a movement to regarding it as the assembly language of the web. i.e. you write your code in something else like Dart or TypeScript or CoffeeScript.
Im not aware of anything thats likely to change that in the forseeable future.
Paid python, ruby, and most recently node.js dev here. Suggestion: http://elixir-lang.org
Just a few points of contention… node *DOES* use multiple threads, they are part of a pool of threads used for checking background/io processes, and can be used via C/C++ modules.
As to difficulty using cluster/fork, most of said changes you have to do when scaling to multiple systems, which is common enough today.
Regarding debuging and testing… if testing is too hard, then odds are what you are testing should be broken into smaller modules. A lot of the time lately, I’ll break out modules for single utility methods. I also work to avoid class chains in favor of plain objects, against utility libraries and methods. The implementations tend to be cleaner, you can avoid the use of “this” and testing becomes less complex.
At this point I’ve worked on a few relatively large projects, where most of the issues were with people trying to use a .Net or Java mindset for solutions written and run against node.js …
Lastly, beyond Promises generators are on the horizon… the project I’m working on right now is using them… Node 0.11.x is far enough along that it’s pretty stable for greenfield development.
From my initial benchmarks, Node doesn’t seem that fast. I’d like to do some real-world tests though.
You’ve used Quad Core that is used by IIS but not node. Multiply your node result by 4 since you can start one instance for every core.
Well I tried Go and I just can’t get over its Java-like type system, missing higher-order functions (map, filter etc.) and error handling verbosity.
Especially the high-order funs…that’s such a useful concept and it’s such a pain programming without it once you get used to it.
Have you tried Clojure? It seems to have got a lot of things right (including concurrency/parallelism).
Hi Tomas — I have coded some Clojure in the past, but not very much. I wrote some as part of my work on the Loggly pipeline, which used Apache Storm (which is written in Clojure). It’s definitely intriguing.
My $0.02 worth.
Hi Darrell — thanks for your comment. I agree with your sentiment. I much prefer writing system software, software for infrastructure that must be highly reliable, so that is my bias. I’ve come to the conclusion that node. js just doesn’t promote the kind of software engineering practices required by those types of systems.
Debug with Nodeclipse in Eclipse or Enide Studio!
The article is provoking.
In the end one must make up his mind for situation at hand and choose set to apply.
Thanks for the interesting article – I’ve been developing in and for node.js for a while, but have yet to look at promises closely. I will no doubt research it now.
Regarding debugging, I will take a risk with a bit of self-publishing, as I have published an npm to instrument your node.js app so you can dynamically decide which functions to track and see their input/output on console: https://www.npmjs.org/package/node-offline-debug.
you should see https://github.com/xicilion/fibjs
Node.js being Chromium V8, makes it fast. Node being single-threaded, makes it easy, in terms of no worries about thread management, whilst still getting maximal performance, at least on one core. If one needs to utilitise more cores for certain functionality, then consider a load balancer for multiple instances, or a queue service for multiple worker tasks, which is anyway an advisable deconstruction.
I do agree with you. I had my brief usage of Node: a couple of production projects and I wasn’t happy with it, I found the code harder to understand and maintain.
I have done a lot of front-end JS so am very familiar with async programming. But when doing Node I just got the impression that doing async code all the time was an unnecessary complication.
Since then I have moved into Go and I am a lot happier with it, you have a nice readable language with goroutines when you need them. I don’t think Node has won or is the future at all.
All the other things like single threaded and etc are solved using 12 factor and are recommended for any backend like ROR and python.