How to access the request context anywhere in your application ?
Tags: node, javascript, webdev
Published: 2023-01-16 18:49:37


![lost track of request context](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1jsq1zyvnqt22be1ljl0.png)
### Losing track of the request context
Languages like Java, C++ are multi-threaded which can have access to request contexts in any part of the thread execution flow or request lifecycle. Whereas, Javascript follows a single-threaded functional flow by default and our required arguments need to be passed through async functions until the request-response lifecycle has been completed. But there could be instances where we lose track of data from the request context that is hitting the application.

We pass headers, query params and body on a request which can be highly crucial. Say we introduce a header `tracking-token` which tracks the HTTP calls in a microservice architecture. This being sent with a request header hitting from a service, we have access to the header in the route file as well as multiple middleware. But there are service layer functions handling heavy logic to get the desired result. Under this scenario, we may have multiple service layer functions being called. Also, we would've introduced more number of routes and more number of functions to handle the routes. At this point of time, it would be cumbersome to pass the `request` as arguments to each callback function in the entire asynchronous cycle to make use of the header named `tracking-token`.

```
// routes.js
app.get('/get-users, () => {
 const userIds = req.users.map(user => user.id);
 await getUsers(userIds);
});

//users.js
const getUsers = async (userIds) => {
 await db.findAllByIds(userIds);
}
```

In the code above we have lost track of the `req` in the `getUsers` function. Now, the only way to pass `req.get('tracking-token')` is as a parameter to `getUsers` function. Imagine we have tens and hundreds of such functions without access to request context and we may need to access it everywhere. Got into a mess right?! 

`AsyncLocalStorage` has got us covered!!

### [AsyncLocalStorage to the rescue](https://nodejs.org/api/async_context.html#class-asynclocalstorage)

There are two ways to approach this. There are projects which use the latest stable version of Node.js and can embrace this and have a go ahead with `AsyncLocalStorage`. But there are applications where upgrading to a newer node version is not an easy step. We'll figure out the former first.

`AsyncLocalStorage` is a class which uses `node:async-hooks` which is stable after Node version 16. We can add a middleware with `AsyncLocalStorage` store which would hold the request context.

```

//storeRequestMiddleWare.js
const { AsyncLocalStorage } = require('async_hooks');

const asyncLocalStorage = new AsyncLocalStorage();

const storeRequestMiddleware = (req, res, next) => {
  asyncLocalStorage.run(new Map(), () => {
    asyncLocalStorage.getStore().set('context', 
        req.get('tracing-token'));
    next();
  });
};

module.exports = {
  storeRequestMiddleware,
  asyncLocalStorage,
};

//app.js

app.use(storeRequestMiddleware)
```

With the `storeRequestMiddleware`, the `tracing-token` from the request header is now held in the store initialised by `AsyncLocalStorage`.

Now, we can fetch the `tracing-token` from the store in any part of the application.

```
//random.js
const traceId = asyncLocalStorage.getStore().get('traceId');
```

> Note: `AsyncLocalStorage` uses [`async-hooks` which is still in experimental state](https://nodejs.org/api/async_hooks.html). Make sure to use the dependency with caution for production-ready applications.

### For older node versions

As for projects using older versions of node, we have the `node-continuation-local-storage` [package](https://github.com/othiym23/node-continuation-local-storage) which solves the same problem but has it's own way of implementation as written below.

```
import { createNamespace } from 'continuation-local-storage';
import { NextFunction, Request, Response} from "express";

export const requestStore = createNamespace('token-store');

export const tracingTokenMiddleware = (req: Request, res: Response, next: NextFunction) => {
    requestStore.run(() => {
        requestStore.set('tracing-token', req.get('tracing- 
              token'));
        next();
    });
};
```

### Drawbacks of `continuation-local-storage`
The package makes use of `async-listeners` which backfires in case of concurrent HTTP calls. We may lose the request context when multiple HTTP calls are made to another service.

It depends on the version of node and the requirement we need to meet when implementing these dependencies. Hope we don't lose track of the required parameters from the request context anymore!