Top 5 Headaches I Had with NodeJS (as a Java Developer)

Alexey Balchunas
6 min readAug 13, 2018

--

I’ve spent most of my career working as a Java Developer. About 4 years ago I had to use JS, CoffeeScript, TypeScript much more. Particularly, I’ve spent a big load of time creating a middle-sized side project completely written in TypeScript: both frontend and backend are in TypeScript.

Here is a list of top 5 headaches I had when developing the NodeJS backend part of the project.

java.lang.ThreadLocal in NodeJS

I know, NodeJS does not have threads, everything is async. However, there are lots of cases when I want to have a place to keep some sort of a context. Here are the three cases where I faced the need of keeping a context:

  • User session. My application services almost always need to know who is accessing their methods: for security checks (is the current user allowed to access the methods?), and user-specific actions (give me data about my user profile).
  • Unit of work. The operations that a service may perform are not always atomic. For example, a user profile may be updated several times during a single operation (e.g. one piece of code may create a record, the other piece may add a role), but you don’t want to hit the database every time the profile is updated. Ideally, you want to flush the changes all at once at the end of the request processing.
  • DB Session Cache. Similar to the previous case. If we’ve read an entity during a service operation, we want to keep that entity in memory until the operation is processed, so that the cached entity is used instead of reading it from the data source again. In the Java world, this is all handled with the help of ThreadLocal. You have a pool of threads handling requests: a request comes, it’s assigned to a thread, the thread handles the request after it’s done the thread is released for future requests. Thus, ThreadLocal can be easily made request scoped. It’s the common way in Java.

Basically, I want java.lang.ThreadLocal, but in NodeJS with promises. For example:

context.runWithNew(() => {
console.log(context.get('user')); // undefined
context.set('user', 'Alexey Balchunas');
console.log(context.get('user')); // Alexey Balchunas
new Promise((resolve, reject) => {
console.log(context.get('user')); // Alexey Balchunas
});
});

This way I would be able to nest promises as much as I wish, while keeping the context.

Now, maybe my google skills are not that great, but it seems like in JS world there is no common way to do that: the built-in feature supporting something quite similar is node’s “domain”, which is deprecated. Also, there are a number of third-party solutions, none worked for me.

So I’ve created a small library implementing exactly what I needed, feel free to use, it’s called ctx4node.

TypeScript Is Not that Type-Safe

The first couple of months with TypeScript it felt great. Then the cruel reality came into play.

In JS it’s very common to break the typings between minor versions of a package. That’s bad, but hey, just use the exact version. However, the problem is that when you do npm install --save-dev @types/react, it prefixes the version with ^, which will make npm to use the last available minor version. For example, you have a lib’s typings in your package.json automatically added by npm:

"devDependencies": {
...
"@types/some-awesome-lib": "^0.0.1"
...
}

As soon as 0.0.2 is available, that's what you'll use in your project. Imagine, that 0.0.2 breaks something for your TypeScript version, it will definitely lead to wasted time at the least minimum.

Thus, I suppose the default behavior of npm assumes that you’re OK with fixing the compile errors time-to-time.

Okay, it’s avoidable, just remember to fix the versions by removing the ^ prefix.

AWS Lambda Cold Start: NodeJS Is as Slow as Java

Well, it’s not completely true, it depends. Here is my take on it.

Some time ago I created an amazing little side-project, its serverless backend was written in Java. As soon as I faced the cold start problem, I started researching. I noticed that many people recommend using NodeJS on AWS Lambda since its cold start is not even relevant.

Then, since my side-project was small yet, I’ve decided to completely rewrite the backend to TypeScript (though it was not the only reason). And what did I find out? A real-world NodeJS app on AWS Lambda is not as fast as some people promised. I know, Java is still slower, the Java project might take 2–3 second to bootstrap, whereas same exact app written for NodeJS takes 1–1.5 seconds.

However, my point is that for end-users cold start of a NodeJS app is as slow as Java app.

JS Does Not Need Dependency Injection

When I started using NodeJS heavily, one of my first thoughts was:

My side project is getting too big, let’s see which DI containers JS world offers

And I’ve found some.

typescript-ioc is my favorite one because it is most likely inspired by Java’s (JSR330), so it looks quite familiar. BTW, if you’re developing a NodeJS RESTful API using TypeScript, I highly recommend typescript-rest by the author of typescript-ioc.

Now, I was surprised, that all the DI containers for NodeJS that I’ve found do not seem to be widely used. Moreover, it looks like JS developers tend to prefer not to use any DI container.

Don’t get me wrong, in Java you almost have to use a DI container, because it is somewhat a glue for many-many libraries: e.g. Spring (the most used framework) is basically a DI container with tons of extensions on top of it.

Whereas in JS there is no such a framework like Spring in Java, so there isn’t any eco-system that you may make use of.

Thus, you’re getting basically 2 things only: easy creation of objects with all its dependencies (though the declaration of dependencies is always hacky in JS due to lack of interfaces and classes) and a convenient way of mocking things in tests. The first one can be easily solved with the built-in import and require. Suppose you have a class A:

export class A {
...
}
export const a = new A();

Then you just use the exported a instance:

import { a } from './a';

export class B {
constructor(private readonly a: A) {}
...
}

// here you basically declare how the B class should be initiated along with its dependencies
export b = new B(a);

Then you may import the created b exactly the same way as we did for a. The only bad side of it is testing. If you need to test the B class with some mocked dependencies, you will have to handle its creation and creation of its dependencies on your own, because you don't have a DI container where you can simply throw your mocks.

Well, that’s just a tradeoff, you sacrifice easy mocking, in turn, you do not introduce another entity/dependency/lib into your project and frankly, your code is much more simple without it. To be honest, I’ve never had a big trouble to test anything so far (with ~80% code coverage).

Standard Collections Are Not Usable

I’m not talking about array and objects. As an experienced Java developer, in JS I’m always missing the standard collections that Java has: Set, Map, List, etc. Now, the newer versions of JS has all the mentioned collections, but they are not quite useful, and the biggest problem is their equality check. They all use just ===:

> new Set([new Date(1), new Date(1)])
Set { 1970-01-01T00:00:00.001Z, 1970-01-01T00:00:00.001Z }

You just cannot use any objects except primitives. The only solution I found is immutable.js, which is not exactly what I wanted, but at least you can define your equals and hashCode methods in order to make use of the collections. BTW, check out how I’ve been practicing functional programming in JS: Why and how to bring immutability to JS world (inspired by Scala).

Sadly, whether you use the standard JS collections or immutable.js, you can feel on every step that the real JS first citizens are arrays and objects. For example, you cannot easily use immutable.js in your DTOs because your UI will get the usual JS array instead of Set or List. There are workarounds for that, but they’ll introduce more problems than they solve.

What’s Next?

If you liked the post, you will definitely love the next post on how I’ve created a serverless multi-regional application on AWS with no much struggle and no serverless frameworks.

Comments, likes, and shares are highly appreciated. Cheers! ❤️

--

--