Course contentsShow
← Node.js
Lesson 564 of 1,9806. Networking and HTTP FundamentalsFree lesson

Context Propagation Across Handlers

Manage request-scoped context using storage patterns to track data through asynchronous chains.

In this lesson, you will learn how to maintain consistent data across asynchronous handlers without passing variables through every function argument.

The Problem: Passing the State

In a standard Node.js server, a single incoming request often passes through multiple layers: middleware, authentication handlers, business logic, and finally the database layer.

If you need to track specific metadata—like a requestID for logging or a tenantID for database filtering—you might be tempted to pass these as arguments to every single function. However, this leads to "prop drilling," where even intermediate functions that don't need the data must accept it just to pass it along to the next layer.

The Solution: AsyncLocalStorage

Node.js provides a built-in mechanism called AsyncLocalStorage to manage "request-scoped" data. It works like a thread-local variable in other languages, but for the asynchronous nature of JavaScript. It allows you to store data at the start of a request and access that same data anywhere in the execution chain—even inside deeply nested callbacks or asynchronous calls—without needing to pass it explicitly.

Putting it into Practice

Imagine you want to capture a requestID the moment a request hits your server. You can "wrap" the entire request chain in a storage context:

const { AsyncLocalStorage } = require('async_hooks');
const requestContext = new AsyncLocalStorage();

// In your middleware
function loggerMiddleware(req, res, next) {
  const store = { requestId: Math.random().toString(36) };
  
  // Anything inside this callback shares 'store'
  requestContext.run(store, () => {
    next();
  });
}

// Deep inside your business logic
function performDatabaseAction() {
  const { requestId } = requestContext.getStore();
  console.log(`Action performed for request: ${requestId}`);
}

By using requestContext.run(), any asynchronous operation triggered within that block—including database queries or file reads—"inherits" the context. If you call requestContext.getStore() deep in your call stack, Node.js automatically knows which request is currently being processed, effectively bridging the gap between disconnected functions.

Key Takeaway: Context propagation uses asynchronous storage patterns to decouple data from function signatures, allowing you to access request-specific metadata anywhere in your execution flow.