DEV Community

Cover image for Request Scoped Context – How I Stopped Passing Arguments Everywhere
Neel-Vekariya
Neel-Vekariya

Posted on

Request Scoped Context – How I Stopped Passing Arguments Everywhere

In my past two backend projects, I use to pass info about user manually. I thought this is the right method. Every time some function need info, I pass it as parameter. Simple. Direct. Clean enough.

Then I started building a cloth rent booking app.

In start everything was fine. I had orderId coming in with the request. I pass it to my booking service. Service pass it to availability checker. I thought — this is clean approach. I feel good about the code.


Then the app start getting more complex.

Now my orderId need to travel from controller to booking service to availability checker to pricing calculator to logger. Every function in this chain need that orderId as parameter.

Even functions which are not using orderId directly they just holding it for a moment and passing it to next function. Function is doing nothing with this parameter except passing it forward.

Like a relay race where some runners are not actually running. They just standing there, holding the baton, waiting to give it to someone else.

This is what called parameter pollution. I didn't know the name then. I just knew something is wrong.

The second problem hit me when I need to add new info deep in the execution. That mean I go back and add that parameter to every function in the chain.

Not because all function use it just because they are sitting in the middle. One change, five files touched. This is tight coupling. Changing something simple create maintenance overhead everywhere.

Third problem is logging. To attach orderId to every log entry I need to carry it all the way down the entire execution path. More parameters. More same work again and again. Copy paste same argument in every function signature.

I was writing same thing again and again. This kind of repetition is not productive. This is just noise.


So I start searching for better approach. How to pass context without manually threading it through every call. And I found something I never heard before.

AsyncLocalStorage.

My first reaction was wow. This is exact solution for my problem.

AsyncLocalStorage is a Node.js feature from the async_hooks module. The idea is simple. You create a storage that is scoped to a specific async execution meaning one request get its own private storage.

You store your orderId, userId, or any context once at the entry point of the request. After that, any function in the entire execution chain can read it directly. No passing. No threading. No pollution.

Node.js track async context internally. When you start a new context using AsyncLocalStorage, every async function running inside it inherit access to that same storage. No matter how deep the call stack goes. The info is just there, available, without anyone carrying it.

To build this in your project you need two functions only.

import { AsyncLocalStorage } from "async_hooks";

const requestContext = new AsyncLocalStorage();

export function runWithContext(context, fn) {
  return requestContext.run(context, fn);
}

export function getContext() {
  return requestContext.getStore() || {};
}
Enter fullscreen mode Exit fullscreen mode

runWithContext is called at the entrance of your request in your middleware or route handler. You pass the context object with whatever info you need, and the function to run. Everything inside that function now have access to the same storage automatically.

getContext is called anywhere inside the execution deep inside a service, inside a logger, inside a helper. It reads from the current async context. No parameters needed. No importing shared state. Just call it and get what you need.


Now my logger not need orderId as a parameter. Just call getContext() and read it. My availability checker not carrying a parameter it don't use. My pricing calculator is clean. Every function only take what it actually need to do its job.

The code became honest. Functions stop pretending to care about things they don't care about.

When I first saw this working in my booking app the feeling was like finally. Not just because code was cleaner. But because I understand why the old approach was a problem in first place.

Small codebase hide these problems. You don't feel the pain until complexity grows. By then bad pattern is already everywhere and refactoring is expensive.

This is one thing I wish I know earlier. Not because it is hard to understand it is actually simple. But because the problem it solve only become visible at certain scale. And when you finally see it, you can't unsee it.

If you are building something today and passing same parameter through three or more functions just to reach one inner function this is your solution.

Store it once at the entry point. Read it anywhere. Let your functions stay focused on what they actually do.




If I made any mistakes in this — please mention in the comments. I'll correct it.

Top comments (0)