@ts-kizuna/core
v1.64.1
Published
Contract and route definition, validation, and the adapter API for ts-kizuna
Downloads
4,080
Readme
ts-kizuna

Build fully typed REST APIs with TypeScript. Write one contract. Get a fully typed server, an OpenAPI spec, Swift and Kotlin clients, and more.
[!NOTE] Why is it already 1.0 if it's in beta? We built ts-kizuna for our own apps and have been battle-testing it in production since before we open-sourced it. The version number came with it from that internal history, so read it as 0.x. It's labeled beta because the syntax for how you write contracts, routers, and clients may still change before v2.
So a minor version can carry a breaking change while ts-kizuna is in beta. Every release names them under ⚠ BREAKING CHANGES, so pin your version and upgrade when it suits you.
Features
- Contract-first: define request/response schemas once, share between server and client
- Type-safe everywhere: full inference on both sides, no casting
- Typed authentication: identities, roles, and per-route access control declared on the contract
- RPC-like client: call your API like a function, get fully typed responses back
- Streaming: stream an AI reply as typed events, yielded from the handler and read with
for awaitin the client - Tools: declare the tools an AI assistant can call, run them on the server, and read each call typed in your client
- Caching: declare a cache policy on a response and every adapter sends
Cache-ControlandVary - TanStack Query: typed query and mutation options with caching and invalidation
- Adapters: mount your API on Express, Fastify, Hono, or Next.js
- HTTP/REST: follows HTTP and REST standards. RFC 9110 semantics, RFC 9457 Problem Details
- Built-in coercion: query, path, and header params are coerced to their declared types (
z.number(),z.boolean(),z.date(),z.bigint()), with no manual parsing orz.coerceneeded - OpenAPI generation: from the same contract, no annotations needed
- Native client generation: typed API clients for Swift (iOS/macOS) and Kotlin (Android/JVM)
- Plugins: extend your API with features built on the contract you already wrote, and get them fully typed in your handlers
- MCP server generation: expose your API as MCP tools so AI assistants can call your endpoints
- Scheduled jobs: declare cron work next to its handler, tick it from any platform scheduler, or run it in process from a route handler
- Deprecation and sunset support: deprecate routes and fields, and it shows up in your editor, OpenAPI, Swift, Kotlin, and the response headers
Getting started
Define your API routes
Export a k instance once. It takes optional tags, identities, request contexts, and validation settings.
// k.ts
import { Kizuna } from '@ts-kizuna/core';
export const k = new Kizuna();Then define each route: pick a method and path, then describe what it takes in and sends back with Zod.
// routes.ts
import { z } from 'zod';
import { ProblemDetailsSchema } from '@ts-kizuna/core/schemas';
import { k } from './k';
export const users = k.routes({
getUser: {
method: 'GET',
path: '/users/:id',
responses: {
200: z.object({
id: z.string(),
name: z.string(),
}),
404: ProblemDetailsSchema,
},
},
});Bundle into a contract
The contract is the single object you hand to the server and the client.
// contract.ts
import { k } from './k';
import { users } from './routes';
export const contract = k.contract({
routes: {
users,
},
});Implement your routes on the server
Handlers get validated, typed input, and their return values are checked against the responses the contract declares.
// server.ts
import { KizunaServer } from '@ts-kizuna/express'; // or any other adapter
import { contract } from './contract';
export const server = new KizunaServer(contract);// router.ts
import { server } from './server';
export const router = server.router({
users: {
getUser: async ({ params }) => {
const user = await db.users.findById(params.id);
if (!user) {
return {
status: 404,
body: {
detail: 'Not found',
},
};
}
return {
status: 200,
body: user,
};
},
},
});Mount it on your app
api.ts is where the router, guards, jobs, and plugins come together, and what the adapter mounts.
// api.ts
import { server } from './server';
import { router } from './router';
export const api = server.api({
router,
});// index.ts
import express from 'express';
import { api } from './api';
const app = express();
app.use(express.json());
api.mount(app);
app.listen(3000);Use the API on the client
Every route, input, and response comes from the same contract, so the client already knows them.
// client.ts
import { KizunaClient } from '@ts-kizuna/fetch';
import { contract } from './contract';
const client = new KizunaClient(contract, {
baseUrl: 'http://localhost:3000',
});
const result = await client.users.getUser({
params: {
id: '1',
},
});
if (result.status === 200) {
result.body; // { id: string; name: string }
}Packages
| Package | Description |
| ------------------------------ | ----------------------------------------------------- |
| @ts-kizuna/core | Routes & contract definition, validation, adapter API |
| @ts-kizuna/fetch | Typed fetch-based client |
| @ts-kizuna/tanstack-query | TanStack Query client |
| @ts-kizuna/express | Express adapter |
| @ts-kizuna/fastify | Fastify adapter |
| @ts-kizuna/hono | Hono adapter |
| @ts-kizuna/next | Next.js App Router adapter |
| @ts-kizuna/openapi | OpenAPI generation |
| @ts-kizuna/swift | Swift client generation |
| @ts-kizuna/kotlin | Kotlin client generation |
| @ts-kizuna/mcp | MCP server generation |
| @ts-kizuna/eslint-plugin | ESLint rules |
| @ts-kizuna/typescript-plugin | Deprecation highlighting in the editor |
| @ts-kizuna/cli | Shared CLI and build tooling |
License
MIT
