DEV Community

Graham Cox
Graham Cox

Posted on

Node.js has a higher cognitive load than Java

I'm going to preface this by saying that I'm a much bigger user of Java than I am of node. I do like node, but I just don't use it as much.

I'm also going to say that it's entirely possible that my lack of experience with node is colouring my thinking here.

I think Java is easier to work with than node. And I know that's going to be a hugely unpopular opinion, and even that people will wonder how I can ever think that. So here's my reasoning.

Imagine I'm writing a simple single page application. I'm going to use:

  • Some form of HTTP server to act as a REST/GraphQL/whatever service.
  • Create React App to build the UI (for arguments sake - I like it)
  • Postgres for the data layer. (See above)
  • Full stack end to end tests using selenium

In the Java world, I can set up a build that allows me to do this. The end result can, remarkably easily, be that I execute mvn clean install to:

  • Download all dependencies
  • Build the backend application
    • Compiling all code
    • Running all unit tests
    • Running static checks - e.g. checkstyle
    • Full integration tests
    • Build a docker image for the application
  • Build the webapp
    • Producing the output files
    • Running all unit tests
    • Build a Docker image for the webapp
  • Build the end up end tests
    • Build a Docker image for the tests

The output of this I can then start the front-end and backend together or individually, and - importantly - I can run a Docker compose stack of the entire system including the end to end tests...

The interesting thing here is the integration tests. I can start up the server, using a library automatically start and stop the database, automatically manage the database schema, then run a whole barrage of tests against it. And all of this is just one thing hidden behind one standard build command, the same used by millions of other projects...

Now let's consider node.

Firstly, as best I know there's no way to do multi module builds in node. This means that I've got to know to build the three different modules individually.

Secondly, the integration tests. Having asked around, it seems that the idea of having an all-in-one process is actively disliked. Instead of running the tests with one command and it does it all for you, it's generally expected that you will:

  • Start up all dependant services yourself - maybe using Docker
  • Start up the service to test yourself - maybe using docker
  • Then run the tests against this
  • Then tear it all down yourself

That's a lot of effort to achieve this.

Thirdly, executing multiple step builds is clunky. Tools like grunt can make it easier, but that's adding one layer of complexity just to hide another. Alternatively you can use shell scripts, but that's not cross platform.

This is only a sample of things, but it already shows that running a build in node is more complicated than in Java. A lot of people argue that each of these steps are significantly simpler than the Java version, and that's true. But the fact that you've got to know it all just makes things more difficult to pick up.

Now, that's not to say it's all bad. There are a lot of good things here to. It's remarkably easy to get a very fast cycle time in node - write code, get unit test results flash up a second later, repeat. Or write code, have the server restarted a second later, and repeat. Java just takes longer to achieve these same things. However, it's got a lot of additional understanding needed by the developer to get to the same point.

At least, that's how it seems to me. I might well be missing things that make all of this a lot easier to understand.

Top comments (19)

dubyabrian profile image
W. Brian Gourlie

And I know that's going to be a hugely unpopular opinion

It's interesting that this is a controversial opinion. Javascript is a language with multiple competing module systems (with the official module system not even available in most runtimes), a nearly non-existent standard library, and a multitude of confusing semantics (variable hoisting, this binding, implicit conversions).

The highly dynamic nature of javascript means static analysis is imprecise, which affects tooling. Large applications need to use bundlers which have a massive learning curve of their own. In order to leverage modern features we often need to run our code through a transpiler that fills in missing functionality. The built in date and time functionality is based on a flawed java implementation circa 1995. It's a tricky language to optimize and there are a number of considerations that need to be taken into account in order to prevent deoptimization.

I could keep going on, but I think the point is clear: Javascript the language is objectively bad. Nobody, given sufficient time and forethought would create a language like javascript in its current form. It is impressive for a lot of other reasons, namely how it came to be and how much effort has gone into making it reasonably usable, but that's a consequence of it being ubiquitous and the most legitimate "write once run everywhere" runtime.

grahamcox82 profile image
Graham Cox

The thing is, JavaScript has a reputation for being used for small, simple things. Whereas Java is better known for large scale enterprise systems. And given the comparison, you immediately will think that JavaScript is simpler to get going with than Java...

chuckwood profile image
Charles Wood

Well, if by small, simple things you mean, "doesn't have tests, complex dependencies, or build steps like transpiling," then yeah ;)

dancrumb profile image
Dan Rumney

This feels like more of a build tool issue than a language issue.

I'll grant you: maven does a lot of stuff out of the box with very little configuration. The JS environment is definitely behind the curve here and a lot of that, in my opinion, is due to the vast array of options over the years (grunt, gulp, webpack, etc)

However, maven can also be a freaking nightmare once you have to deal with competing dependencies or if dependencies contain overlays.

None of this really contributes to the cognitive load of the language; rather, I think this speaks to the fact that the JS ecosystem hasn't yet reached the level of standardization that one experiences when dealing with Java.

I also think that the Java ecosystem has had the benefit of the attention of engineers with experience building large systems for longer and so have felt (and dealt with) the pain points that these entail.

snowfrogdev profile image
Philippe Vaillancourt

I tend to agree. Although I have almost no Java experience, I can say that it's not all that hard to achieve the kind of all in one build process you're talking about in Node.js using NPM scripts and module loaders like webpack.

grahamcox82 profile image
Graham Cox

So - and I'm seriously interested here - how do you achieve:

  • Build all of the code
  • Run all of the unit tests
  • Run all of the Integration tests
    • Start up a temporary Postgres Database running the correct version
    • Set up the correct schema in the database
    • Start up the service being tested
    • Run all of the tests
    • Shut everything down

The Integration Tests part of that in particular I've always struggled with. The best I've been able to come up with is using Docker and Docker-Compose, but not only is that a bit flakey - getting the startup ordering right is non-trivial - but it's yet another tool for developers to need in order to run the full stack tests.

Thread Thread
snowfrogdev profile image
Philippe Vaillancourt

There are many ways to achieve this, and it does involve some initial work on the part of the developer, but once everything is setup, it is a thing of pure beauty. I'll give you an example of the build process for a small framework I'm working on, it might not touch on all the points you mention, but it should be enough to make you a (at least partial) believer. If you want to take a deeper look, check it out here.

So, with one command, git push here is what happens:

  • Runs all unit tests using jest
  • Transpiles TypeScript to JavaScript
  • Bundles up the code using rollup
  • Automatically generates API documentation using ts-doc
  • Pushes commits to the remote (github)
  • Starts up a build on Travis CI where it will:
    • Install all dependencies
    • Run all unit tests
    • Transpile TypeScript to JavaScript
    • Bundle up the code using rollup
    • Automatically generate API documentation using ts-doc
    • Send test coverage info to coveralls
    • Push API documentation to GitHub pages
    • Analyze the commits to see if there are changes that warrant a patch, minor or major version release.
    • If there is, modify package.json file to include new version number.
    • Push new release to NPM
    • Add release information to
    • Commit the new package.json and to GitHub remote.

Of course, your build process would not need many of these steps and would add some that aren't there. But the point is that, with one command, all these automated steps were triggered and you can make that happen too.

How? It all depends on the tools, libraries, services and environments that you use. But the basis for all this is NPM scripts, that you'll normally find in the package.json file. You might have to write a few custom Node scripts to do some fancier stuff but it's not all that complicated and is well worth it to automate the entire process. Hope this helps.

Thread Thread
grahamcox82 profile image
Graham Cox

So, without wanting to sound flippant, what's the one command that someone who has only just checked out the repository can run to achieve all of that? And the one command that can be run before a commit - to make sure that I've not introduced any breaking changes in the commit I'm about to do?

It looks like it's npm run prepush, but it's not obvious to me.

And this is exactly my point. It's not obvious, because there is so much flexibility and so many different ways to achieve things.

In the Java/Maven world, I know that I can run mvn install on any project to run the complete build/test/etc lifecycle. This one command will:

  • Download all of the dependencies
  • Run all static checks
  • Perform any code generation steps needed
  • Perform any resource generation steps needed
  • Compile all of the code - main and test
  • Package everything up correctly
  • Run all of the tests - unit and integration

Additionally, if I ran mvn site then it will build the full set of documentation for the project. And if I instead ran mvn deploy then it will deploy the output to the configured destination - e.g. putting artifacts into Archiva, sending the actual application to Heroku, sending the built site to S3, etc.

And all of this works exactly the same on a single module or across a multiple-module build.

And the Integration tests can include steps to start and stop dependant services if needed - e.g. docker containers, the actual service under test, etc.

And packaging everything up can include into WAR files, executable JAR files, Docker containers, whatever is needed.

But the key thing is - I don't have to think about it. As someone new to the project, I already know how to work with it. As someone doing day-to-day work with it, it's easy.

(And, before you say, I know a bunch of that is because it's what I know, and that if I was more used to Node, or Python, or something like that then this would probably seem overly complex and that language would seem simpler.)

Thread Thread
snowfrogdev profile image
Philippe Vaillancourt

Super interesting. Like I said, I have next to no practical experience with Java, and the Java ecosystem, so it looks like I misunderstood exactly what you meant. What you are describing is very cool. I guess you summed it up nicely when you said:

there is so much flexibility and so many different ways to achieve things.

And that is a problem in the JavaScript universe, not only with build processes, but with pretty much every aspect of coding and development. It's the whole convention vs. configuration thing and in the JavaScript world, we are big time in the configuration side of things. There are so many ways to do everything. JavaScript fatigue is a real thing.

So you definitely have a point; you can't expect that running npm build on one JS project, will have the same impact as running npm build on another JS project. There is no convention when it comes to that.

grahamcox82 profile image
Graham Cox

I absolutely agree. However, for most people the build tool is their first interaction with a new project. And for the developers the build system is their day-to-day interaction. So a build system that makes life easy is hugely beneficial.

And then there's the fact that the build system is generally tied to the language. (Ignoring things like make...)

stealthmusic profile image
Jan Wedel

We are doing the common Angular FE/ Java Backend. From the start, it amazes me all the time how complicated it is to get a working environment running, installing all the right versions an so on. Now we build with maven-frontend-plugin which at least downloads the correct node/npm versions and then runs the build.

grahamcox82 profile image
Graham Cox

maven-frontend-plugin is awesome. I love it for combined builds. As is the docker-maven-plugin from Spotify, and the postgresql-embedded dependency from Yandex. Between those, and a well set up Pom file, the only thing that I ever have to worry about version wise is the JVM and Maven versions - everything else is set in stone.

What's more, using that stack, the only versioning concern at all is that the live database matches the one in the tests. I can build Docker containers of the actual services that contain the exact JVM to run on, so even that isn't a concern.

myrcutio profile image

I'd argue the cross platform support is a bit moot at this point. With Windows 10 supporting a native Ubuntu vm, nobody has a reason not to have a bash terminal on their machine. Best practice from my experience has been to have a bash scripts folder and a set of npm commands to instantiate environments. I tend to WANT my codebases built separately, preferably through CI and and deployed to a container repo. Or even better, full serverless. The Serverless framework has some pretty fantastic offline support for testing, and it's a breeze to deploy from there (handles teardown as well). It's two commands instead of one, to do a docker compose and a serverless-offline startup, but it gives you a remarkable level of flexibility in your stack. Combine that with something like chakram for testing and you have some robust end to end tests in a repeatable environment.

grahamcox82 profile image
Graham Cox

So, at my day job, Windows 10 isn't supported yet. We've got people on Windows 7 and Windows 8, but the security and IT people have yet to officially roll out Windows 10 to anyone. And I know of other, bigger organisations where Windows 8 isn't even supported yet!

Just because the latest and greatest version of the OS supports these things doesn't mean everyone is using it. And if I can do things that make life easier for people in general, I don't see any reason not to.

nbageek profile image
Patrick Minton

I am inclined to agree with you that getting from 0 to prototype is easier in Java than it is node.js. This is also true of Ruby, PHP and Python, by the way, where things like Rails, Laravel, and Django give you a platforms with great scaffolding abilities.

However, I think node is closing ground here fast. What it has lacked heretofore is an opinionated framework such as Rails that lets you set up a lot of scaffolding and boilerplating fast, precisely because that opinionated philosophy takes away a lot of choices and makes automating this easier.

But that opinionated framework comes with drawbacks. It's a lot harder to optimize your apps because the frameworks optimize for development time, not performance. It's a pain to do weird/custom things, like if you want some highly optimized SQL, have fun fighting with your ORM.

Furthermore, this kind of thing will come to Node.js too -- javascript is too popular for it not to. Meteor is something you might want to check out, and yeoman probably already has a lot of what you are looking for. A lot of times yeoman drives me a little nuts because it seems like every generator template has about 80% of what I want and none are perfect, but YMMV.

zeerorg profile image
Rishabh Gupta • Edited

I haven't made a full production app in Java, but have done that with Django and another one with Node. And testing Node app is a disaster. Django tests are pretty easy to write and it handles all the database setup and teardown easily. I can write tests in nodejs too, but I have to mock out the whole database interaction which is a big task in itself and then cross my fingers and rely on the fact that database interaction will work as expected.
I think Java and some languages like PHP and Ruby have these frameworks which work so awesomely for big apps and NodeJS still lacks those.

sait profile image
Sai gowtham

I think you like writing more lines of code for simple things

redhoodjt1988 profile image
Jonathan Reeves

This is a great article. I recently started to go down the road of learning Java with Springanf Spring Boot. I came from JavaScript/TypeScript. I prefer Java but am pretty comfortable with JavaScript environments. If you are looking for something similar to Rails, Django etc then I would suggest NestJS. NestJS, or Nest as it is more commonly refered to, is an opinionated framework that sets up tests, files and build all in one app. It's a great tool I've been using it professionally for almost a year. Using Next helpede better understand Spring Boot concepts such as annotations, interfaces and writing tests. I think you'll find that Nest does a really good job of handling all of the features you mentioned.

As a JavaScript/TypeScript developer that has started to use Java more I prefer to use Java. But Nest makes a great substitute for Spring if you're in a JavaScript environment.

thomasjunkos profile image
Thomas Junkツ

This argument is totally broken.

The headline is prototypical for that. While Java is a language, nodejs is not.

Another highlight: When talking about the good things in Java, you are talking about a built tool written in Java. We all should know that Java as well as Javascript are turing complete. So that it should be valid to say: when I am able to solve a problem in Java, I should be able to solve that problem in Javascript. So when there is Maven for Java the only reason, that it doesn't exist in Javascript is, that nobody has written a port yet.

Then: when talking about the goodness of Java you talk about many things completely unrelated to the language.

Finally what you should have written is:
The Javascript ecosystem lacks mavenlike tooling which let's me work with comparable workflows.
Or if you want it a bit more clickbaity: Java tooling is more mature than Javascript tooling - which opens up a discussion.