We went away from node as a backend technology for a bunch of reasons. Here's a list of the biggest pain points:
- Lack of a good standard API; compared to environments like Java, C# or Go, node's standard library is significantly sparse.
- The tendency for small libraries/frameworks leads to a very high number of third party code with all the problems attached; bigger attack surface, licensing challenges, it's economically impossible to vet and review dependencies
- There's a tendency in the ecosystem to abandon projects rather soon (~1-2 years) and to keep changing things. Further, we have had several situations where maintainers did not respect semver, combined with npm's approach of updating patch versions upon installation, we have had too many broken builds from one day to another w/out code changes. The state of documentation of a lot of projects is non-existent.
- Lack of multi threading. We have used all the options, including RPC implementations, but that doesn't even come close to approaches like Java threads or go routines. Neither in performance, nor in maintainability.
- Lack of typing. That's probably the biggest one. Yes, we use TypeScript, quite extensively even. But TypeScript brings its own problems. First, it's only declarative. If you have a `something: number`, there's no guarantee that it's actually a number upon execution, so if you have a bug in a layer interacting with another system, that might fail a couple levels deep. You hence end up with type checks at some places and you cannot really trust it anyway. Second, TypeScript's tooling is slow and has some annoying quirks (e.g. aliases not being resolved upon compilation). Having aliases allowing to shorten import paths is a big, big win, though. Third, the typing, given the complexity of JavaScript, can be confusing, sometimes even seemingly impossible to get right.
Is node a bad technology? Not at all. I'd not chose it for enterprise, big or long-lived projects, though. It's a very good technology for a lot of things, especially smaller projects. We are building on Java + Spring Boot now.
To each their own. I was a Java engineer for most of my career since the early 00s, switched to Node a couple years ago (primarily running Apollo GraphQL server in Node), and I find myself so much more productive now. Addressing each of your points:
1. Yes, I think it's true that server-side Node isn't really usable without tight reliance on NPM. That said, I think NPM has improved by leaps and bounds over the past few years and it now makes it quite easy to have a stable and secure set of dependencies. Integrating our build with npm audit (and audit-resolver) and other tools like snyk, using package lock files, and keeping dependencies up-to-date on a regular schedule has worked very well for us.
2. Again, I think the NPM ecosystem has settled down in the past few years and I don't see as much churn. While there has been some issues with large projects (e.g. lodash and moment) going into hibernation, that's been fine for us.
3. I've found all of our uses for multithreading were better served by having an event system publish events that were then handled by serverless functions. The lack of multithreading, and the way Node manages concurrency, has been a godsend. Just check out the recent GitHub report where they were accidentally leaking information from other users into their sessions.
4. Typescript (combined with autogenerating typescript type files from GraphQL schema definitions) has been honestly heaven for us, and the benefits I've seen with the structural-based typing of TS made me realize the huge number of times I had to battle the nominal-based typing of Java and the immense pain that caused.
Thanks to you and the parent comment both for this rundown on the pros and cons, I found it helpful; and also, using a throwaway account to defend nodejs is, if intentional, a fantastically subtle joke.
Meh, check my post history. I created a throwaway account years ago to comment on some somewhat controversial topic back in the day. I just forgot to throw it away.
If you've come from java and you like node, maybe you should spend some time with the alternatives?
A big part of it depends on what your exact requirements are but my experience with node didn't bite me for quite a while.
1/2) my experience is that even the supported packages have had glaring holes where they don't in other languages. Just to give a quick example, I had a project that used node-cache-manager to implement a tiered cache. There was a bug (in the cache library with the most stars) just last year where the cached values in a memory cache were passed by reference as opposed to copied. That meant any mutation on them affected other fetches from the cache! That would never happen in java. This particular bug took weeks to debug in production because values were being randomly mutated. After the fix, it also had different behaviour for when the cache value was new vs when it was retrieved. So two mutation bugs in the same cache codebase see https://github.com/BryanDonovan/node-cache-manager/issues/13....
I'm not blaming the author, he's a really good guy. What i'm saying is this is a wart both in the language and the library ecosystem - it's not unreasonable to expect a sensible caching library.
3) I agree that threads aren't necessarily the way to go. But can we agree that a language that CAN efficiently take advantage of multiple cores would be better? It's not just for your application. It's also for any compiling eg. typescript!
> Just check out the recent GitHub report where they were accidentally leaking information from other users into their sessions.
Concurrency is hard! except in a language where it isn't. In elixir each "thread" (erlang process) would get a different copy of the data so this type of bug doesn't happen.
4.
> Typescript (combined with autogenerating typescript type files from GraphQL schema definitions) has been honestly heaven for us, and the benefits I've seen with the structural-based typing of TS made me realize the huge number of times I had to battle the nominal-based typing of Java and the immense pain that caused.
That is an interesting assessment. I've never really noticed a difference in practice between structural/nominal type systems to the extent that i didn't realise typescript was structural. Normally if you have multiple classes implementing the same structure, you want an interface anyway to make sure they don't diverge i.e. there is a higher purpose for them being the same.
Would you have an example of how this would be a deal breaker?
I think besides this aspect, Kotlin might be up your alley.
I would upvote this comment a hundred times if I could. I had got a Node project in my previous gig and I hated it every minute. The team got immensely productive when we migrated it to Golang.
In the times of RESTful APIs and µServices, is there really a need for node? JVM, C#, Elixir, Golang etc provide excellent development experience and are battle tested the way single-threaded Node just hasn't been.
The main advantage of Node is that you're already using Javascript on the client side, and this lets you share code. If you've got business logic that you want to use on both client and server, keeping them in synch between two different languages is a nightmare. It's pretty much the epitome of Repeating Yourself.
That said, it's kind of a narrow domain, where your server-side logic is simple enough that you can successfully write it in Node, but complex enough that there's code you can't afford to keep synched.
How do you feel about the impact on developer productivity after migrating to Java + Spring Boot? I haven't used Java in a long while and every time that I try to come back to it, I get driven away by the difficulty and complexity to do simple things (thinking of annotations, dependency injection, complicated design patterns). It feels like an effort of one hour of Node or Python programming (or even Go) would take 10x more in Java.
Great question. I was very concerned about that, too. I strongly recommend learning Domain Driven Design before diving into Spring, you will recognize a lot of patterns and it then makes sense how things work. Even things like having an interface + an implementation for basically everything becomes apparent. I had a lot of bad experience with Spring and Jakarta EE in the past, but I can assure you Spring Boot does a fantastic job here.
Point being, it is an enterprise setup and in enterprise applications you favor maintainability and friends over speed of development. So implementing a feature takes more files and more time, in a DDD setup you can estimate something like 1 controller method, use case + interface, >=2 DTOs, potentially a domain model if not present plus a repository + interface if not present.
So yes, it is more overhead and you have a slow down in productivity. I have no proof, but I am 95% sure that the time you save upfront in a more flexible environment like node is being paid back significantly worse when it comes to maintenance.
Edit: you can of course use Spring Boot without enterprise/DDD and hence make it simpler. It's actually pretty minimal.
+1 for DDD in any strong-type-first language like Java or C#. Even if you land in a system that was designed horizontal-layer "at speed", you can retrofit good language, aggregate boundaries and migrate to a world away from spaghetti. Speaking as someone who usually works on code bases 10+ years old.
Thanks for the thoughtful answer. Do you have any recommendation of a good book to solidify my DDD understanding? I've read one in the past (can't remember the name) and that one felt like IT consulting bullshit :)
This is why choosing the right language for the job is so important.
I know PHP has a bad reputation, but I personally think it offers an excellent middle ground for web projects. It has some great frameworks which have been used at scale while not having all the complexity and rigidness of Java.
As a dev that's used Java, PHP and Node in the past I perfer working on PHP and Node projects because I feel far more productive and less constrained, but I can certainly appreciate why a larger corporate project might choose Java.
Arguing about the perfect language seems pointless without some idea of the requirements.
> How do you feel about the impact on developer productivity after migrating to Java + Spring Boot.
I can answer that as we just strangler-pattern'd a legacy nodeJS app to Golang. Abstracting away from Node and JS paradigm is bit harder, but the blame totally lies on the callback ridden code we end up with Node. It also forced us to think about our DataStructures, abstractions and module structure. The efforts took comparatively the same time as it took the original team to write the Node app. Also I wouldn't put Golang and Python in the same league as Node.
> effort of one hour of Node or Python programming (or even Go) would take 10x more in Java
Modern Java (and Kotlin) is surprisingly powerful and the days of AbstractFactoryBeanFacadeInterface are behind it.
I've got to say that I'm more tempted to start something with Java + Lombok than Kotlin. Somehow it feels like I'll have an easier time with the tooling, but I'm probably exaggerating.
I maintain a Java Spring Boot service full time, and when something stops working or doesn't do what you expect, it can be a total nightmare to debug. There is so much misdirection, it can be incredibly difficult to figure out which code will be executed, in which order.
I try to make things as explicit as possible, which can help in testing and debugging.
Spring makes simple things easy and difficult things impossible. Spring Boot was created to mitigate some of the mess Spring is, specifically that mere mortals can not assemble version compatible libraries that comprise Spring so spring Boot does it for them.
> I get driven away by the difficulty and complexity to do simple things (thinking of annotations, dependency injection, complicated design patterns). It feels like an effort of one hour of Node or Python programming (or even Go) would take 10x more in Java.
You are comparing stuff that is super helpful on large project with how to be quick in 1 hour by one person. If I have 1 hour long project in java, I wont use complicated design patterns. If I dont need Spring Boot or dependency injection, I wont use them. I am not sure what you mean by complicated annotations tho.
In Java, if all what you want is a method that processes some data, you wont use any of that and will just do a class or something like that. So if python have better methods to achieve whatever you want to achieve out of the box, it will be somewhat faster. But it will have zero to do with annotations.
I agree that the way I've made my statement can be ambiguous. What I meant was when using Node/Python/Go for projects of similar complexity. Maybe not millions of lines of code, but a web app with some order processing, database access and specific business logic. It feels harder in every way when using Java + Spring Boot, but my question was sincere: I'm pretty sure that this combo is a successful platform and I want to understand why.
Finally, by excessive use of annotations, I think I'm talking about ORMs. I remember checking a Hibernate-based project in the past and some methods had more lines of annotations than actual code. I'm not sure this is bad, but it kind of creates a new dimension of code for me to wonder about.
2. Yet to be seen. Deno has few good modules like deno cliffy and drash which are structured nicely and easy to use. Otherwise, plenty of matured library for web and node can be used too.
3. Yet to be seen. Module management in deno is very explicit and there are no magic updates.
4. Deno support workers with an additional sandbox layer for multi threading. There are few proposals to solve cumbersome worker setup (requiring a separate file) in the pipeline. They will get standardized soon.
5. Deno has first class support for typescript. Type checking speed is a bit faster due to some optimizations and you can use no-check in development to run your typescript code. That will give it a huge boost.
Having type checking at runtime would be great but it would add significant performance penalty. There were some attempts to add it to typescript via plugins but nothing panned out.
6. You can share much more code between deno and browser. It is more compatible with web than node. Huge win for switching context for front-end developers and using the same skill set.
Other than that, deno cli will feel exactly like go and cargo. It tries to provide a similar tool chain and UX which node ecosystem lacks. That should feel fast (most stuff is written in rust except the type checking part).
At the moment, they are trying different approaches to speed up type checking. One is to convert swc AST to something that tsc can understand.
Agreed. I've been on a few projects where other people were doing node.js. It's fine but my take away from that experience is to not use it for something that matters and that it's rarely the best tool for the job. These projects have a tendency to get ugly in a hurry.
The good news is you can do real stuff with it relatively easy and that all you need to know is javascript. Which is great if you have some frontend people getting their feet wet with backend stuff. But most of what it does you can do in other tech stacks as well and they tend to be pretty good at the stuff node is used for.
Go is decent. Python is decent (forget about threading though, global interpreter lock is still a thing). I use a lot of Kotlin & Spring Boot myself. There are many other tech stacks. Each of those has rich ecosystems with great libraries, frameworks, tooling, etc. Whether you are doing batch jobs, data engineering, server code, etc. They each have many options for technology.
I got really into Node for a backend around 2015. I loved it, its execution was fast enough, I could speed through feature implementations, it was so much easier to build things compared to the 5 million line Java project from my previous employer.
Then a not-so-competent team member joined and it made working in JS complete hell. I had to be so much more careful on code reviews because there was no compiler to catch common mistakes, and still issues slipped through.
TypeScript is here now and that's great, it solves some of the issues I mentioned, but I moved on to Rust (and stopped doing web stuff for the most part) and I'll trade my implementation speed for easier code reviews and majorly reduced debugging time.
Share code between client and server using WebAssembly[1]. The Twitch video player is written in C++ via WASM[2]. C# can be "full stack" with Blazor[3]. Rust can be "full stack" with Yew[4]. Similar support exists for other languages including Go[5] and even the TypeScript-syntax AssemblyScript[6].
- Lack of a good standard api: I don't understand this one. I've never had a problem with its documentation and the API always seemed fine to me, but I don't use C# or Go so maybe I'm missing out?
- There's a tendency in the ecosystem to abandon projects rather soon: This can certainly happen, but it's important to choose libraries and frameworks carefully. My node servers (Express.js) are extremely light weight, so I rarely have this problem.
- Lack of multi threading: I think you are wrong on this point but I don't know your expectations since I'm not a Java developer. I have written some multi threaded code and saw massive perf improvements. You can't expect a scripted language to perform like Java, but quality of code is also very important. I wrote a generator that replaced a Java utility that was 10x faster. I rarely have to worry about this because it's easier to write async code that's extremely fast.
- Lack of typing: this seems more like a criticism of javascript.
I get that this isn't the platformm for you, but I think you might be wanting it to be too much like what you are familiar with.
I don‘t personally work with node for the backend but typescript/javascript in the browser. And I think what is meant is something like LINQ in C# (all kinds of functions you can apply on enumerables/arrays), some basic DateTime library (how to add two dates in JS? How to get the diff of two dates in days or hours in JS? Both are 1 liners in C#) and maybe some more stuff for string mutation and so on.
There’s work on new JS standard called Temporal that’s a stage 3 proposal. People should be using its poly fill instead of other data libraries where possible.
Who maintains day.js? It‘s (i did not look, i guess) not the same people that develop the JavaScript standard library. At some point, day.js is abandoned or breaks something and nobody is going to fix it because it is abandoned, or even worse, soneone creates a fork and fixes it there and now we have 2 day.js.
If it is in the standard library, you have a guarantee that it works today, works tomorrow and with a quite high certainty still works the day after. There are not that many languages that successfully break backwards compatibility often.
And don‘t get me started with the legal aspect. There are many projects where one cannot simply pull another dependency just to calculate a date diff.
What makes the Date constructor a good candidate for the standard library but not the aforementioned methods? Not everyone knows about Day.js, you had to learn that at some point. Why Day.js instead of Moment or Luxon or Date-Fns? There's a lot of cognitive value to having a fully featured standard library.
That why we read and learn. You can't expect node to work like java. I feel like I'm talking to a wall with you guys. I've been working with JS for 15 years. I would never presume to start Java dev and expect to do things my way. I saw the same thing when Java devs were trying to learn Ruby... you have to approach with a different mentality.
Regarding Moment, on their site and they plaster exactly why you should not use moment and explain exactly why other libs (Day.js included) are better. Node is designed to be modular, Java isn't.
That‘s not the point. Nobody expects that Node/Js does exactly the same as Java or C#. If so, one could simply use Java or C#.
A language should provide a set of functionality that makes the developers life easier and consists of a set that he can achieve basic (daily) things without the need of 3rd-party dependencies. And if that usually starts with „npm -i“ I‘m going to question that...
We are going to have to agree to disagree then. Both Node and frontend js share the same package management system (npm). Everything is modular because it has to be. When you work fullstack JS and use one package manager for everything, it makes all the sense in the world why things are done the way they are. The same code has to work in Node, Blink, Webkit, Electron, etc.
Node's lack of a robust standard library results in significant dependency bloat, even for small projects. I often choose other languages (Racket, Python) for most projects because of this. I tend to consider a large number of dependencies as a risk for the stability and maintainability of projects. Through this lens, adding date handling to the standard library can make sense, I think.
Was the Java utility one that started and didn't run for very long, like a command line application? The JVM (at least the Oracle one) takes a long time to start up, so any application that runs on the JVM takes a long time to start up. The JVM is much better for long running processes.
A vendor gave us a .jar cli tool for managing an enterprise product last year, and I swear it starts up in under half a second (to the familiar spring boot splash too), so it is possible, just uncommon :)
- Lack of a good standard API; compared to environments like Java, C# or Go, node's standard library is significantly sparse.
- The tendency for small libraries/frameworks leads to a very high number of third party code with all the problems attached; bigger attack surface, licensing challenges, it's economically impossible to vet and review dependencies
- There's a tendency in the ecosystem to abandon projects rather soon (~1-2 years) and to keep changing things. Further, we have had several situations where maintainers did not respect semver, combined with npm's approach of updating patch versions upon installation, we have had too many broken builds from one day to another w/out code changes. The state of documentation of a lot of projects is non-existent.
- Lack of multi threading. We have used all the options, including RPC implementations, but that doesn't even come close to approaches like Java threads or go routines. Neither in performance, nor in maintainability.
- Lack of typing. That's probably the biggest one. Yes, we use TypeScript, quite extensively even. But TypeScript brings its own problems. First, it's only declarative. If you have a `something: number`, there's no guarantee that it's actually a number upon execution, so if you have a bug in a layer interacting with another system, that might fail a couple levels deep. You hence end up with type checks at some places and you cannot really trust it anyway. Second, TypeScript's tooling is slow and has some annoying quirks (e.g. aliases not being resolved upon compilation). Having aliases allowing to shorten import paths is a big, big win, though. Third, the typing, given the complexity of JavaScript, can be confusing, sometimes even seemingly impossible to get right.
Is node a bad technology? Not at all. I'd not chose it for enterprise, big or long-lived projects, though. It's a very good technology for a lot of things, especially smaller projects. We are building on Java + Spring Boot now.