AsyncLocalStorage (ALS) is a powerful but often misunderstood feature in Node.js. At its core, ALS provides a reliable way to maintain execution context across asynchronous operations—a challenge every Node.js backend eventually faces.
If you’ve ever needed to keep track of metadata such as request IDs, tenant identifiers, locales, or feature flags across nested async/await calls, ALS is the tool designed for that job.
This article explains what AsyncLocalStorage is, how it works, and how to apply it effectively using practical examples.
What Is AsyncLocalStorage in Node.js?
AsyncLocalStorage is part of the async_hooks module and functions similarly to thread-local storage in multi-threaded languages—except adapted for Node’s event-loop and asynchronous model.
It creates a context store that persists across all asynchronous boundaries triggered within a specific scope.
This enables predictable context propagation through:
- Promises
- Timers (
setTimeout,setInterval) - Database clients
- External API calls
- Streams
- Message queue handlers
- Any nested async function
Core benefit: You no longer need to pass metadata manually through every function call.
Why AsyncLocalStorage Matters for Modern Node.js Applications
Maintaining context is important for:
- Request correlation and structured logging
- Distributed tracing
- Multi-tenant routing
- User or locale propagation
- Feature flag consistency
- Tracking background job executions
ALS is fundamentally about context propagation, not just logging.
Architecture: How ALS Propagates Context
Incoming Request / Task
│
▼
┌────────────────────────┐
│ Initialize ALS Context │ ← store.run(...)
└───────────┬────────────┘
│
▼
Handlers → Services → Async Calls → DB/API → Response
│
▼
The ALS context remains accessible everywhere
ALS creates a scope once, and all subsequent async operations continue to use the same context.
Minimal AsyncLocalStorage Example
import { AsyncLocalStorage } from 'node:async_hooks';
const store = new AsyncLocalStorage();
store.run({ value: 123 }, () => {
setTimeout(() => {
console.log('Context:', store.getStore());
}, 50);
});
Output:
Context: { value: 123 }
Even across timeout boundaries, the context is preserved.
Using AsyncLocalStorage in an Express Application
Initialize Context per Request
import { AsyncLocalStorage } from 'node:async_hooks';
import express from 'express';
import { randomUUID } from 'crypto';
const asyncStore = new AsyncLocalStorage();
const app = express();
app.use((req, res, next) => {
const context = {
requestId: req.headers['x-request-id'] || randomUUID()
};
asyncStore.run(context, () => next());
});
Access the Context Later in the Flow
function getContext() {
return asyncStore.getStore();
}
app.get('/users', async (req, res) => {
await new Promise(resolve => setTimeout(resolve, 100));
const ctx = getContext();
console.log('Request ID:', ctx.requestId);
res.json({ requestId: ctx.requestId });
});
This is the classic request ID correlation pattern and is widely used for production observability.
Other Practical AsyncLocalStorage Use Cases
Beyond correlation IDs, ALS supports several operational patterns:
1. Multitenancy Context
asyncStore.run({ tenantId: 'acme' }, () => handleRequest());
2. Locale & Internationalization
{ locale: req.headers['accept-language'] }
3. Feature Flag Snapshot
{ featureFlags: evaluateFlags(user) }
4. Background Job Tracking
worker.on('job', job => {
asyncStore.run({ jobId: job.id }, () => processJob(job));
});
These examples rely on the same mechanism: execution-scoped context.
When Should You Use AsyncLocalStorage?
| Use Case | ALS Recommended? |
|---|---|
| Request/Execution scoped metadata | ✔ Yes |
| Request ID correlation | ✔ Yes |
| Distributed tracing | ✔ Yes |
| Multi-tenant context | ✔ Yes |
| Passing large mutable objects | ✘ No |
| Global config | ✘ No |
ALS should store lightweight metadata, not application state.
FAQ: Common Questions About AsyncLocalStorage
What is AsyncLocalStorage used for in Node.js?
It maintains execution-scoped context across async operations, enabling request correlation, multitenancy, tracing, and more.
Is AsyncLocalStorage only for request IDs?
No. Request IDs are just one example. ALS can hold any metadata relevant to the async execution scope.
Does ALS work with async/await?
Yes. ALS propagates through promises, timers, callbacks, and most async libraries.
Can ALS be used outside Express?
Yes. It works in Fastify, NestJS, message queues, CRON jobs, and any async workflow.
Is there a performance cost?
A small one, but acceptable for most workloads. ALS is used by major frameworks and tracing tools.
Summary
AsyncLocalStorage is a foundational tool for building reliable, observable Node.js applications. It provides a clean and efficient way to maintain execution context without manually passing metadata through every function. While request ID correlation is the most common pattern, ALS supports many other practical use cases such as multitenancy, localization, feature flags, and background job correlation.
If you're building production-grade Node.js services, understanding ALS will significantly improve your architecture, debugging capabilities, and observability.
Top comments (0)