Recently I’ve been working on a Node.js project where we needed to keep track of which requests were generating calls to some other pieces of code. Passing a request identifier around was not viable since it would require to change too many APIs.
We solved the problem using cls-hooked, a small package that uses the
async_hooks experimental Node.js API to implement Continuation Local Storage.
What is Continuation Local Storage?
Some languages have Thread Local Storage. This is a way to attach global data to the thread itself. Due to the asynchronous and single-threaded nature of Node.js this is not useful. Instead, we use Continuation Local Storage (CLS).
CLS is a mechanism that allows the attachment of data to the current asynchronous execution context. It uses async_hooks to keep track of asynchronous context changes and to load and unload the data associated with it.
Building an Express middleware
First we need to create a namespace. Namespaces are a way to scope global data so we don’t end up getting values from somebody else. We create a namespace with the
Our express middleware is going to be quite simple. We’re going to generate an
uuid and store it in the namespace.
As you can see, the
set method is called within a
requestId value is going to be available from any code called inside that
run block, and that’s why we are calling
next() inside the
run as well. Calling
next() inside the
run will make the
requestId available for any other middleware and controller in our express application.
We can now build our complete express application and use the
A word on performance
Even though the solution presented above is clean and requires little coding, it has a major drawback: the usage of the
async_hooks API is still experimental so it could possibly change in the future, but what is worse is that enabling it has a big impact on performance. This is currently being discussed in this github issue.
If you are writing a critical piece of code you probably want to stay away from
async_hooks. You’d also want to stay away from
async_hooks if you’re writing a library, since you could impact performance for your users just for your convenience. If the problem you’re dealing with does not match neither of these scenarios, CLS may be a great way of making things simpler.