Getting Started

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


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

Stage 01RUNTIME

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
Dispatch Mechanism: Web Standard Request / Response interface
Supported Runtimes: Vite Dev / Prod, Nitro Presets, Next.js App Router, Express, Fastify, Fetch hosts
Virtual Routing: Compiled static manifest with zero filesystem lookups at runtime
server.tsTypeScript
// Taser.js receives Web Standard Request
const response = await taserApp.fetch(request);
1 of 7

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:

src/routes/admin/users/$id.get.ts
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 DimensionApplication Context (createContext)Middleware State (ctx.state)
Locationsrc/context.tsFolder layouts ($.ts, admin.ts, _auth.ts)
LifecycleBoot (singletons) & Per-Request hooksPer-request during middleware pipeline traversal
ScopeGlobal: Accessible across every route and middleware in the appScoped: Accessible only to child routes located under that directory
Access SyntaxDirect root properties: ctx.db, ctx.logger, ctx.requestIdNamespaced state property: ctx.state.user, ctx.state.org
Declaration TypeUniversal type parameter in createTaserApp().context(...)Inferred automatically from return next({ ... }) in layouts
Primary Use CasesDatabase pools, Redis clients, queue producers, correlation IDsAuthenticated 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:

src/context.ts
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:

src/routes/admin/$.ts
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.