Context and State
Learn how to manage application singletons and request-scoped state with createContext. Understand boot context, request context, and native runtime interop.
Taser.js features a dual-layer dependency injection system created with createContext(). It separates long-lived application singletons (boot context) from ephemeral per-request metadata (request context).
Dual-Layer Context Overview
import { createContext } from "@taserjs/router";
import { PrismaClient } from "@prisma/client";
import { createClient as createRedisClient } from "redis";
export const context = createContext({
// 1. Boot Context: Initialized once when the server starts
boot: async () => {
const db = new PrismaClient();
await db.$connect();
const redis = createRedisClient();
await redis.connect();
return {
db,
redis,
env: process.env.NODE_ENV ?? "development",
};
},
// 2. Request Context: Initialized on every incoming HTTP request
request: (req: Request) => {
const requestId = req.headers.get("x-request-id") ?? crypto.randomUUID();
const startTime = Date.now();
return {
requestId,
startTime,
};
},
});Attach your context in src/taser.ts:
import { createTaserApp, type InferAppContext } from "@taserjs/router";
import { context } from "./context.js";
export default createTaserApp().context(context);
export type AppContext = InferAppContext<typeof context>;Boot Context vs Request Context
| Feature | Boot Context (boot) | Request Context (request) |
|---|---|---|
| Execution | Runs once when the application boots up | Runs once for each incoming request |
| Async Support | Fully async (async () => ({ ... })) | Sync or async ((req: Request) => ({ ... })) |
| Best Used For | Database pools, Redis connections, SDK clients, configuration | Request IDs, timing markers, trace headers, tenancy IDs |
| Performance | Zero per-request overhead | Lightweight object allocation per request |
Accessing Context in Handlers
Every route handler and middleware automatically receives the combined properties of boot and request context directly on the ctx object:
import { json, notFound } from "@taserjs/router/reply";
import { t } from "@taserjs/router";
export default t.get("/users/:id").handler(async (ctx) => {
// Available from boot context:
const user = await ctx.db.user.findUnique({
where: { id: ctx.params.id },
});
if (!user) {
return notFound({ message: "User not found" });
}
// Available from request context:
const elapsed = Date.now() - ctx.startTime;
console.log(`[${ctx.requestId}] User ${user.id} fetched in ${elapsed}ms`);
return json(user);
});Typing Extracted Helper Functions
When writing helper functions that take ctx as an argument, extract the context type from the route builder variable (GET, POST, etc.) before calling .handler():
import { json, notFound } from "@taserjs/router/reply";
import { z } from "zod";
import { t } from "@taserjs/router";
// 1. Declare builder variable
const GET = t.get("/users/:id").params(z.object({ id: z.string() }));
// 2. Extract RouteContext from the builder
export type RouteContext = typeof GET.$Infer.Context;
// 3. Type your helper functions with RouteContext
async function getUserProfile(ctx: RouteContext) {
return ctx.db.user.findUnique({
where: { id: ctx.params.id },
});
}
// 4. Call helper inside .handler()
export default GET.handler(async (ctx) => {
const user = await getUserProfile(ctx);
if (!user) {
return notFound({ message: "User not found" });
}
return json(user);
});Accessing the Standard Request in createContext
The request hook receives the standard Web Request object for each incoming dispatch:
import { createContext } from "@taserjs/router";
export const context = createContext({
request: (req: Request) => {
const userAgent = req.headers.get("user-agent") ?? "unknown";
const clientIp =
req.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ??
req.headers.get("x-real-ip") ??
"127.0.0.1";
return {
userAgent,
clientIp,
requestId: crypto.randomUUID(),
};
},
});Reserved Context Keys
To avoid collisions with internal routing properties, the following property names are reserved and cannot be returned by createContext:
statequeryparamsbodyheaderscookiesmethodpathurlrequest
If you attempt to return a reserved key from createContext(), TypeScript will generate a compiler error on the createContext() call.
Next Steps
Layouts and Middleware
Compose shared logic, authentication guards, and cascading typed state with layout files. Understand middleware execution ordering and state propagation.
Extracting Helper Functions
Extract reusable route handlers and split large API endpoints into small, testable modules with Taser.js compile-time Context type inference.