async-context-middleware Overview
The asyncContext middleware makes the current request context available from anywhere in your code -- without passing it through every function argument. It uses Node.js AsyncLocalStorage to propagate context through the entire call stack of a request.
The Problem: Passing Context Everywhere
In a typical web application, you often need request information (like the current user, a request ID, or session data) deep inside utility functions, database helpers, or logging code. Without async context, you have to pass this information through every function in the chain:
// Without async context -- context must be passed everywhere
router.get(routes.posts, async ({ context }) => {
let user = getUser(context)
let posts = await getPosts(user, context)
await logAccess('viewed posts', context)
return html`...`
})
async function getPosts(user, context) {
await auditLog('fetching posts', context) // Must pass context
return db.query(...)
}
async function auditLog(action, context) {
let requestId = context.get(RequestId) // Just to get one value
// ...
}This creates verbose function signatures and makes refactoring painful. Every new utility function needs a context parameter.
The Solution: AsyncLocalStorage
AsyncLocalStorage is a Node.js API that stores data for the duration of an asynchronous operation. Think of it as a "thread-local" variable for async code -- each request gets its own isolated storage that follows it through await calls, Promise chains, and callbacks.
The asyncContext middleware stores the request context in AsyncLocalStorage at the start of each request. Then, anywhere in your code, you call getContext() to retrieve it:
// With async context -- no need to pass context around
import { getContext } from 'remix/async-context-middleware'
router.get(routes.posts, async ({ context }) => {
let user = getUser()
let posts = await getPosts(user)
await logAccess('viewed posts')
return html`...`
})
async function getPosts(user) {
await auditLog('fetching posts')
return db.query(...)
}
async function auditLog(action) {
let context = getContext()
let requestId = context.get(RequestId) // No parameter needed
// ...
}When to Use Async Context
Async context is useful when:
- Logging: Include request IDs, user info, or URLs in every log message without passing them through.
- Auditing: Record who did what from deep inside service functions.
- Multi-tenancy: Access the current tenant from database utilities.
- Error tracking: Attach request metadata to error reports.
You do not need async context when:
- Your handlers are simple and do not call deeply nested functions.
- You are comfortable passing context as a function argument.
- You are deploying to an edge runtime that does not support
AsyncLocalStorage.
Quick Example
import { createRouter } from 'remix/fetch-router'
import { asyncContext, getContext } from 'remix/async-context-middleware'
let router = createRouter({
middleware: [
asyncContext(),
],
})
// Somewhere deep in your code
function getCurrentRequestUrl() {
let context = getContext()
return context.url.pathname
}Place asyncContext() early in the middleware chain so all downstream middleware and handlers can use getContext().
Next Steps
- Tutorial: Share Context Across Functions -- Step-by-step guide to using async context for logging, user info, and more.
- API Reference -- Complete documentation of
asyncContext()andgetContext().