Sentry Logo Debug Microservices & Distributed Systems

Join my free newsletter

Level up your dev skills and career with curated tips, practical advice, and in-depth tech insights – all delivered straight to your inbox.

5 min read
Up to date
advanced

Trevor I. Lasn

Staff Software Engineer & Engineering Manager

AsyncLocalStorage: Simplify Context Management in Node.js

How AsyncLocalStorage solves context management in asynchronous Node.js apps

AsyncLocalStorage gives you a way to maintain context across your async operations without manually passing data through every function. Think of it like having a secret storage box that follows your request around, carrying important information that any part of your code can access.

Here’s what a typical Express application without AsyncLocalStorage might look like. We need to pass the userId through multiple functions:

Notice how we keep passing userId everywhere? Now multiply this by several more parameters like requestId, tenantId, and locale. The function signatures grow unwieldy fast.

Here’s how we can clean this up with AsyncLocalStorage

The context follows the request through its entire lifecycle. No more parameter passing.

When to Use AsyncLocalStorage

Here’s where AsyncLocalStorage shines: tracking requests as they flow through your microservices. You can log the request ID, trace ID, and other metadata without passing them through every function.

AsyncLocalStorage keeps track of the current transaction across your database operations. This is useful when you need to pass the transaction object through multiple functions:

AsyncLocalStorage is also useful for logging. You can store the current log context and retrieve it in any function.

When you run this, each log message automatically includes the full request context:


When Not to Use AsyncLocalStorage

While AsyncLocalStorage is powerful, I avoid it when:

  • The context only needs to flow through a couple of functions - regular parameter passing is clearer.
  • Working with synchronous code - AsyncLocalStorage adds unnecessary complexity.
  • When you build a public API using AsyncLocalStorage, you’re forcing a specific way of managing context onto your users. Consider a payment processing API:

Your API consumers now must wrap every call in storage.run(), which might not fit their application’s architecture.

They can’t simply call processPayment() directly with a user ID. Instead of writing straightforward code like await processor.processPayment(100, userId), they’re forced into this more complex pattern:

A more flexible approach would make the context optional while still supporting AsyncLocalStorage

This approach gives API consumers the freedom to choose their preferred approach while maintaining compatibility with AsyncLocalStorage when needed. They can either use the context storage or pass parameters directly, fitting their specific use case and architecture.

AsyncLocalStorage relies on the Node.js event loop to maintain context. This means operations running outside the event loop (like setImmediate or process.nextTick) won’t have access to the stored context.

AsyncLocalStorage might seem like magic at first, but understanding its boundaries helps you use it effectively. When used appropriately, it significantly cleans up your code and makes context management a breeze.


Become a better engineer

Here are engineering resources I've personally vetted and use. They focus on skills you'll actually need to build and scale real projects - the kind of experience that gets you hired or promoted.

Many companies have a fixed annual stipend per engineer (e.g. $2,000) for use towards learning resources. If your company offers this stipend, you can forward them your invoices directly for reimbursement.


This article was originally published on https://www.trevorlasn.com/blog/node-async-local-storage. It was written by a human and polished using grammar tools for clarity.

Interested in a partnership? Shoot me an email at hi [at] trevorlasn.com with all relevant information.