How to access the request context anywhere in your application ?

earthboundmisfit

Muzammil

Posted on January 16, 2023

How to access the request context anywhere in your application ?

lost track of request context

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);
}


Enter fullscreen mode Exit fullscreen mode

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

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)


Enter fullscreen mode Exit fullscreen mode

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');


Enter fullscreen mode Exit fullscreen mode

Note: AsyncLocalStorage uses async-hooks which is still in experimental state. 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 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();
});
};

Enter fullscreen mode Exit fullscreen mode




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!

💖 💪 🙅 🚩
earthboundmisfit
Muzammil

Posted on January 16, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related