Routes
Declare HTTP endpoints in Inspire and route requests into server handlers.
Routes define the HTTP surface of your app for callers outside the generated-client path. Use them for webhooks, partner integrations, machine-to-machine APIs, and external systems that talk to your app over plain HTTP.
For your own frontend, exposed functions plus @kizaki/client are the better default. Routes are the escape hatch when the caller is not your generated client.
Basic Shape
routes("/v1", auth: apiKey(read)) {
GET /projects -> listProjectsRoute
POST /projects -> createProjectRoute
}A route declaration specifies:
- Path
- HTTP method
- Handler function
- Auth mode
Grouping, Prefixes, And Overrides
Route groups share a prefix and default settings. Per-route overrides appear only where behavior changes.
routes("/v1", auth: apiKey(read), rateLimit: 1000/minute) {
GET /projects -> listProjectsRoute
GET /projects/:projectId -> getProjectRoute
POST /projects -> createProjectRoute (auth: apiKey(write))
DELETE /projects/:projectId -> deleteProjectRoute (auth: apiKey(write))
routes("/admin", auth: apiKey(admin)) {
GET /users -> listUsersRoute
DELETE /users/:userId -> deleteUserRoute
}
}Auth Modes
session
Use when the caller is a signed-in user.
routes("/app", auth: session) {
GET /me -> getCurrentUserRoute
POST /projects -> createProjectRoute
}Provides normal user identity, policies, and tenant scoping.
apiKey(scope)
Use for machine-to-machine access.
routes("/v1", auth: apiKey(read)) {
GET /projects -> listProjectsRoute
GET /projects/:projectId -> getProjectRoute
}The scope name comes from the apiKeys block. Use for partner APIs, ingestion endpoints, and automation.
none
Use only when the route has its own verification model, such as a signed webhook.
routes("/webhooks", auth: none) {
POST /stripe -> stripeWebhookRoute
}The handler must verify the caller independently.
Handler Model
The schema owns the routing table. Your server code owns the handler logic.
import { Project } from "@kizaki/schema";
import {
RouteRequest,
badRequest,
created,
insert,
ok,
query,
select,
} from "@kizaki/sdk";
export async function listProjectsRoute(_req: RouteRequest) {
const rows = await query(
select(Project)
.fields(Project.id, Project.name)
.orderBy("createdAt", "desc"),
);
return ok(rows);
}
export async function createProjectRoute(req: RouteRequest) {
const input = req.body<{ name?: string }>();
if (!input?.name) {
return badRequest("name is required");
}
const [project] = await query(
insert(Project)
.values({ name: input.name })
.returning(),
);
return created(project);
}Routes are thin HTTP boundaries. Translate HTTP concerns into application workflows; keep business logic in reusable server code.
Request Surface
export async function getProjectRoute(req: RouteRequest) {
const projectId = req.params.projectId;
const format = req.query.format;
const method = req.method;
const path = req.path;
const requestId = req.headers["x-request-id"];
const rawBody = req.rawBody;
const body = req.body<{ includeArchived?: boolean }>();
return ok({ projectId, format, method, path, requestId, rawBodyLength: rawBody.length, body });
}| Property | Use |
|---|---|
req.params | Path parameters like :projectId |
req.query | Query-string values |
req.headers | Auth signatures and integration metadata |
req.rawBody | Webhook signature verification |
req.body<T>() | JSON request payloads |
Response Helpers
import {
badRequest,
created,
error,
forbidden,
noContent,
notFound,
ok,
redirect,
unauthorized,
} from "@kizaki/sdk";
ok({ id: "p_123" });
created({ id: "p_123" });
noContent();
redirect("/login");
badRequest("invalid payload");
unauthorized("not authenticated");
forbidden("missing scope");
notFound("project not found");
error(422, "unprocessable request");Webhooks
Webhook routes use auth: none with handler-level verification.
routes("/webhooks", auth: none) {
POST /github -> githubWebhookRoute
}import { RouteRequest, badRequest, ok } from "@kizaki/sdk";
export async function githubWebhookRoute(req: RouteRequest) {
const signature = req.headers["x-hub-signature-256"];
if (!signature) {
return badRequest("missing signature");
}
// Verify req.rawBody with the provider SDK or shared helper here.
// Then hand off to normal application logic.
return ok({ received: true });
}Recommended Pattern
- Keep route declarations short and structural
- Group by path prefix and auth model
- Use
apiKey(scope)for machine access - Reserve
nonefor webhook-style verification flows - Use exposed functions and the generated client for your own frontend
- Keep business logic in reusable server code, not inline in handlers
Related docs: