Next.js AsyncLocalStorage Route Handler
Middleware wrapper for Next.js Route Handlers that executes requests within an AsyncLocalStorage context.
Installation
npm install @nextwrappers/async-local-storage # npm
yarn add @nextwrappers/async-local-storage # yarn
pnpm add @nextwrappers/async-local-storage # pnpm
Usage
// app/api/hello/route.ts
import { asyncLocalStorage } from "@nextwrappers/async-local-storage";
export const { wrapper: asyncLocalStorageWrapped, getStore } =
asyncLocalStorage({
initialize: () => "Hello from AsyncLocalStorage!"
});
export const GET = asyncLocalStorageWrapped(() => {
console.log(getStore()); // "Hello from AsyncLocalStorage!"
return new Response("OK");
});
By wrapping the route handler with asyncLocalStorageWrapped
, we can access the AsyncLocalStorage store from anywhere within the callback execution with a call to getStore()
.
📝
Use-Cases Request Tracing
traced
wrapper
Together with uuid
, we can trace a request through through execution and log throughout.
First we create a wrapper that initializes the store with a traceId
:
// utils.ts
import { asyncLocalStorage } from "@nextwrappers/async-local-storage";
import { v4 as uuid } from "uuid";
import { NextRequest } from "next/server";
export const { wrapper: asyncLocalStorageWrapped, getStore } =
asyncLocalStorage({
initialize: (request: NextRequest) => ({
traceId: uuid(),
pathname: request.nextUrl.pathname
})
});
Now we define a simple logger that logs the traceId
and the message:
// lib/logger.ts
import { getStore } from "./utils";
export const logger = (prefix: string, message: string) => {
const { traceId, pathname } = getStore() || {};
console.log(`[${traceId}] (${pathname}) ${prefix}: ${message}`);
};
Finally, we can use the logger in our route handler:
// app/api/hello/route.ts
import { asyncLocalStorageWrapped, logger } from "lib";
const doSomething = async () => {
logger("doSomething", "Doing something!");
return new Promise((resolve) => setTimeout(()=>{
logger("doSomething", "Done!");
resolve("OK");
}, 1000));
};
export const GET = asyncLocalStorageWrapped((request: NextRequest) => {
logger("GET", "Request started!");
const response = new Response(doSomething());
logger("GET", "Request finished!");
return response;
});
This will log something like this:
[1b9c0b0a-7b5a-4b9f-8f9c-8b0c0b0a7b5a] (/api/hello) GET: Request started!
[1b9c0b0a-7b5a-4b9f-8f9c-8b0c0b0a7b5a] (/api/hello) doSomething: Doing something!
[1b9c0b0a-7b5a-4b9f-8f9c-8b0c0b0a7b5a] (/api/hello) doSomething: Done!
[1b9c0b0a-7b5a-4b9f-8f9c-8b0c0b0a7b5a] (/api/hello) GET: Request finished!