Typed Client SDK
Connect to Taser.js backends with end-to-end type safety using @taserjs/router-client. Enjoy autocomplete for routes, query schemas, and return types.
The @taserjs/router-client package provides a lightweight, zero-codegen proxy client that infers end-to-end type safety directly from your server's router type definition (RouteManifest or typeof app).
1. Initialize the Client
Install the client package in your project:
pnpm add @taserjs/router-clientInitialize the client with your backend baseUrl and your server app or manifest type:
import { createClient } from "@taserjs/router-client";
import type { RouteManifest } from "../.taser/types/routes.js";
// Or when using virtual modules / fullstack adapters:
// import type { routeManifest } from "#taserjs/virtual/manifest";
// import type { app } from "@/.taser/entry";
export const api = createClient<RouteManifest>({
baseUrl: process.env.NEXT_PUBLIC_API_URL ?? "http://localhost:3000/api",
headers: async () => {
const token = localStorage.getItem("auth_token");
return token ? { Authorization: `Bearer ${token}` } : {};
},
});Explicit End-to-End Type Safety
By passing your server's RouteManifest or typeof app type as a generic to createClient<TApp>(),
the client automatically infers all available routes, methods, input schemas, and return contracts without generating static client source files.
Path Segment Conventions
The client uses nested proxy property access mirroring your URL paths. Certain URL conventions are translated into valid JavaScript property names:
| URL Pattern | Client Property Path | Parameter Passing |
|---|---|---|
/ | api.$get() | api.$get() |
/products | api.products.$get() | api.products.$get() |
/users/:id | api.users._id.$get() | api.users._id.$get({ param: { id: "123" } }) |
/files/* | api.files._splat.$get() | api.files._splat.$get({ param: { _splat: "docs/readme.txt" } }) |
/.well-known/jwks | api.$well_known.jwks.$get() | api.$well_known.jwks.$get() |
/user-profiles | api.user_profiles.$get() | api.user_profiles.$get() |
Calling API Routes
GET Request with Query Parameters
// Calls GET /api/products?category=electronics&page=1
const response = await api.products.$get({
query: {
category: "electronics",
page: 1,
},
});
if (response.ok) {
const data = await response.json();
// Auto-inferred from handler reply helpers or returns[200] schema
console.log(data.products);
}Dynamic Path Parameters (_param)
For parameterized routes like src/routes/users/$id.get.ts:
// Calls GET /api/users/user_456
const response = await api.users._id.$get({
param: { id: "user_456" },
});
if (response.ok) {
const user = await response.json();
console.log(user.name);
}POST Request with JSON Body
For routes like src/routes/posts.post.ts:
// Calls POST /api/posts with Content-Type: application/json
const response = await api.posts.$post({
body: {
title: "Building type-safe APIs with Taser.js",
content: "Deterministic file routing with cascading context.",
},
});
const createdPost = await response.json();Per-Request Options
Pass a second argument to any HTTP method to configure request-level options like one-off headers, a custom fetch implementation, or standard RequestInit settings:
const response = await api.posts.$post(
{
body: { title: "Draft Post" },
},
{
headers: { "X-Idempotency-Key": "req_abc123" },
init: { cache: "no-store" },
},
);Response Typing & Handling
Every client method returns a Promise<ClientResponse<TJson>>, which wraps the standard Web Response object:
res.statusandres.okreflect runtime HTTP status codes.res.json()returnsPromise<TJson>with success payload types inferred automatically —.returns()is optional.
.returns() Is Optional for Client Typing
By default, TJson is inferred from handler reply helpers (json(), ok(), created(), etc.) as the union of successful ReplyOf payload types (200–226). Add .returns({ 200: schema }) only when you want server-side contract enforcement, runtime response validation, or to override client inference with an explicit schema type.
Type Resolution Precedence
- Handler inference (default): When
.returns()is omitted,TJsonunions successfulReplyOf<Status, Body>types from the route handler. returns[200]override: When.returns({ 200: schema })is defined,TJsonuses that schema's output type.- Fallback:
unknownif neither source is available.
const response = await api.users._id.$get({
param: { id: "user_123" },
});
if (response.ok) {
const data = await response.json(); // Typed from handler or returns[200]
console.log(data.email);
} else {
console.error("Request failed with status:", response.status);
}Multipart File Uploads (formBody)
Use formBody() to upload binary files and structured form fields with multipart/form-data:
import { formBody } from "@taserjs/router-client";
const avatarFile = new File([blob], "avatar.png", { type: "image/png" });
// Calls POST /api/users/user_123/avatar with multipart form payload:
const response = await api.users._id.avatar.$post({
param: { id: "user_123" },
body: formBody({
label: "Profile Picture",
file: avatarFile,
}),
});Server-Side File Validation
On the backend, your body schema validates incoming File objects with native file schemas like
zod's z.file(), checking max size and allowed MIME types. Learn more in the Multipart & File
Validation Guide.
Inferring Request & Response Types
Extract TypeScript types from client endpoint methods using helper generics:
import type { InferRequestType, InferResponseType } from "@taserjs/router-client";
import type { api } from "@/lib/api";
// Infer input arguments for an endpoint:
type UserGetInput = InferRequestType<typeof api.users._id.$get>;
// { param: { id: string }; query?: ... }
// Infer successful JSON payload:
type UserData = InferResponseType<typeof api.users._id.$get>;Configuration Reference
Client Initialization Options (CreateClientOptions)
Prop
Type
Per-Request Options (ClientRequestOptions)
Prop
Type
Next Steps
Deployment Presets
Deploy Taser.js APIs across Cloudflare Workers, Vercel, AWS Lambda, Node.js, Bun, Deno, and Netlify using multi-cloud Nitro server presets.
CLI & Type Generation
Generate ambient route types, scaffold empty endpoint files, and enforce pre-build CI checks with the @taserjs/router-cli developer tool.