Lessons and Pitfalls Coming To Node.js From PHP

Having worked almost exclusively in PHP for several years, eventually the Node.js hype caught up with me when my team decided to adopt it for the development of microservices. I was skeptical about that choice mainly because while I was quite open to switching away from PHP, I would have preferred to switch to a language I thought would be actually better. JavaScript was not on my language wish list. Then again, JS has come quite a way in recent years, and there’s no denying how huge its ecosystem has become, so as a professional developer, I was ignoring it at my own peril. It certainly took a while to learn the ropes and get used to some of its more quirkier features, but now I at least feel confident enough to blog about some of them. Here are five aspects of my PHP-to-JS transition that stood out to me:

Type checkers save lives

PHP’s type system isn’t great, but at least it has one (and to be fair, it is steadily improving). JavaScript, on the other hand, does not offer any type safety whatsoever, which I find makes it way too easy to mess up and make small mistakes that cause everything to blow up at runtime. Fortunately, I’m not the first one to think so, which has resulted in the creation of not just one, but two transpilers that extend JavaScript with type annotations, both backed by big names: Microsoft’s TypeScript and Facebook’s Flow. Both type systems are actually substantially better than PHP’s, providing such “luxuries” as generic and function types. TypeScript has recently gained a lot of traction, but in my team we use Flow, so that’s what I have experienced so far. The one thing I am lamenting about this choice is that Flow does not have a private modifier for class properties, which TypeScript does. (I consider the ability to have private properties to be one of the greatest advantages of OOP, therefore any language that does not support it can’t possibly be a good and proper OO language.) That aside, it does its job well enough and it integrates nicely with PhpStorm (though it needs to be explicitly switched on first). Still, the fact that many third-party libraries are only supported through separately downloadable type definition files – and the additional fact that those are not always 100% accurate – serve as occasional reminders that these type systems are tacked onto rather than built into the core language.

Mind your awaits

async/await might be one of the most exciting features of Node.js, and it is likely the one that takes the most getting used to for someone new to this style of programming. Personally, I found forgetting the await when calling an async function to be my biggest challenge. Several times I found my application behaving erratically or crashing, only to realize eventually that I had missed an await. Fortunately, Flow can point out some of those errors, but not all.

catch all the things

One advantage of PHP’s one-process-per-request model is that many types of failures are naturally limited to the scope of the request in which they occur. This allows PHP developers to be rather sloppy in their error handling: The worst that will usually come out of an uncaught exception or fatal error is an Internal Server Error response to the client, but the overall availability of the service won’t be affected. In a Node.js application, where one single process is responsible for handling all requests, we have to be more cautious to make sure no error escapes us and brings down the entire service.

Not everything is an array

One of PHP’s better known shortcomings is that it only comes with one built-in type of data structure – the array – that doubles as both a list and a map/dictionary/hash. JavaScript, like most programming languages, has separate Map and Array classes, which is nice, but it means that PHP developers need to get used to not being able to iterate over a map’s values the way they can do it with associative arrays, but having to call .values() first. Even more pitfall-y is the fact that .values() does not return an array, but an iterator that can only be traversed once, so if we need to use it more often, we have to explicitly convert it to an array using Array.from().

Less refactoring safety

When working in PHP, I love PhpStorm’s refactoring capabilities. There really is no good reason why I’d want to rename an identifier by hand instead of pressing Shift+F6 and making the IDE do it, ensuring that all of the identifier’s occurrences are accounted for, wherever in the codebase they may be. These refactorings are also available in JavaScript, but unfortunately they are not quite as reliable there as in PHP. It happened to me multiple times that I renamed a variable, only to find out later that the IDE had applied the same refactoring to another, wholly unrelated variable in a different file that just happened to have the same name.

Conclusion so far

While working with Node.js, my skeptical attitude towards it has mellowed somewhat. Now I’d still favor PHP for conventional web applications – mostly for the possibility of using Symfony, which I’m quite fond of –, but lately I have been working on headless background services such as event listeners, for which PHP’s process-per-request model makes it not a natural choice. Node.js lends itself to this kind of application better, and I have to admit I’m a fan of not only being able to use asynchronous I/O, but doing so in a very elegant way thanks to async/await. I would not want to do anything serious in pure JavaScript without TypeScript or Flow, though.

Share