Core Concepts
Master Taser.js architecture: four core pillars, request lifecycle flow, context injection, cascading middleware, and compiler-enforced return contracts.
Taser.js is designed to eliminate the ambiguity and unsafe typecasting common in Node.js backend development. To get the most out of Taser.js, it helps to understand its four architectural pillars.
The Four Pillars
1. Filesystem Routing
Deterministic file conventions define URLs and HTTP verbs automatically without manual route registries.
2. Dual-Layer Context
Separate long-lived boot singletons (databases, caches) from request-scoped metadata (IDs, timing) with createContext.
3. Cascading Pipelines
Middleware state returned in folder layouts flows down the directory tree directly into ctx.state with full inference.
4. Bidirectional Safety
Standard Schema validates incoming params, query, and bodies, while .returns() enforces outgoing response shapes.
The Request Lifecycle
Every HTTP request handled by Taser.js flows through a deterministic, type-safe pipeline:
Taser.js Request Execution Pipeline
Click any stage to inspect lifecycle execution, data flow, and type inference
Runtime & Platform Resolution
The incoming HTTP request is received by the platform runtime and dispatched into Taser.js's radix tree via a Web Standard Request object.
Execution Highlights
// Taser.js receives Web Standard Request
const response = await taserApp.fetch(request);1. Platform Resolution
When an incoming request reaches your application (Vite Standalone, Nitro presets, Next.js, or an Express, Fastify, or Fetch-native host), the runtime invokes taserApp.fetch() with a Web Standard Request object.
2. Context Initialization
Taser.js evaluates the application context configured in src/context.ts:
- Boot Context: Singletons created once at application startup (database connections, Redis clients, external API clients).
- Request Context: Properties created per request (unique
requestId, incoming timestamp, user agent).
3. Cascading Middleware Execution
Taser.js runs layout middleware from outermost to innermost:
- Root layout (
src/routes/$.ts) - Parent directory layouts (for example,
src/routes/admin.ts) - Pathless layouts (for example,
src/routes/admin/_auth.ts)
Any state returned by middleware (such as next({ user: currentUser })) merges cleanly into ctx.state.
4. Input Validation
Before the route handler executes, Taser.js validates params, query, headers, and body against their schemas. If validation fails, a ValidationError is raised and caught by .onError().
5. Handler Execution
The route handler receives a fully typed context object containing all validated inputs, injected middleware state, and application singletons.
6. Response Contract Verification
At compile time, TypeScript checks that the value returned from json() satisfies the schema declared in .returns(). If runtime response validation is enabled, Taser.js also verifies outgoing payloads in development.
Anatomy of the Context (ctx) Object
Within any route handler or middleware, the ctx argument provides structured, type-safe access to every aspect of the request:
import { json } from "@taserjs/router/reply";
import { z } from "zod";
import { t } from "@taserjs/router";
const GET = t
.get("/admin/users/:id")
.params(z.object({ id: z.string() }))
.query(z.object({ detailed: z.coerce.boolean().default(false) }));
export type RouteContext = typeof GET.$Infer.Context;
export default GET.handler(async (ctx) => {
// 1. Injected from Boot Context (src/context.ts)
const db = ctx.db;
// 2. Injected from Request Context (src/context.ts)
const requestId = ctx.requestId;
// 3. Injected from Layout Middlewares (src/routes/admin.ts)
const adminUser = ctx.state.currentUser;
// 4. Validated Path Parameters
const userId = ctx.params.id;
// 5. Validated Query Parameters
const isDetailed = ctx.query.detailed;
// 6. Web Standard Request and URL
const request = ctx.request;
const url = ctx.url;
return json({ userId, adminUser, requestId, isDetailed });
});Context Field Reference
Prop
Type
Context vs Middleware State
Understanding when to place properties in Application Context (createContext) versus Middleware State (ctx.state) is essential for maintaining clean architecture:
| Architectural Dimension | Application Context (createContext) | Middleware State (ctx.state) |
|---|---|---|
| Location | src/context.ts | Folder layouts ($.ts, admin.ts, _auth.ts) |
| Lifecycle | Boot (singletons) & Per-Request hooks | Per-request during middleware pipeline traversal |
| Scope | Global: Accessible across every route and middleware in the app | Scoped: Accessible only to child routes located under that directory |
| Access Syntax | Direct root properties: ctx.db, ctx.logger, ctx.requestId | Namespaced state property: ctx.state.user, ctx.state.org |
| Declaration Type | Universal type parameter in createTaserApp().context(...) | Inferred automatically from return next({ ... }) in layouts |
| Primary Use Cases | Database pools, Redis clients, queue producers, correlation IDs | Authenticated user sessions, RBAC permissions, tenant metadata |
Code Comparison
1. Global Infrastructure in src/context.ts
Use createContext to inject server singletons and universal request properties:
import { createContext } from "@taserjs/router";
import { dbPool } from "./db.js";
export const context = createContext({
// Initialized once at server boot
boot: () => ({
db: dbPool,
logger: console,
}),
// Evaluated once per incoming HTTP request
request: (req) => ({
requestId: req.headers.get("x-request-id") ?? crypto.randomUUID(),
startTime: Date.now(),
}),
});2. Conditional Scoped State in src/routes/admin/$.ts
Use next({ ... }) in layout middleware to compute and pass typed state downstream:
import { t } from "@taserjs/router";
// State returned in next() cascades into all routes inside src/routes/admin/
export default t.layout("/admin/*").use(async (ctx, next) => {
const token = ctx.headers.get("authorization");
if (!token) {
throw new Error("Unauthorized");
}
const user = await ctx.db.verifyToken(token);
return next({
user, // Typed User object available as ctx.state.user
role: "admin" as const,
});
});No Global Interface Merging
Unlike traditional Express where you must declare global Express.Request namespace overrides,
Taser.js infers middleware types through lexical scope and folder hierarchies.
Related Guides
Manual Installation
Step-by-step guide to installing and configuring Taser.js manually with Vite, Nitro deployment presets, Next.js, and host pass-through dispatching.
Migration Guide
Incrementally migrate existing Express or Fastify APIs to Taser.js with zero downtime using the host pass-through architecture.