What is Kizuna?
A spec-driven framework for building fully typed REST APIs in TypeScript, where your route declarations produce the validation, the documentation, the clients, and the AI tools.
You write routes, each a method, a path, the Zod schemas it takes and returns, and the handler that serves it:
export const getUser = k
.route({
method: 'GET',
path: '/users/:id',
auth: 'user',
responses: {
200: UserSchema,
404: ProblemDetailsSchema,
},
})
.handler(async ({ params, throwError }) => {
const user = await db.users.find(params.id); // the `:id` in the path above
if (!user) {
return throwError({
status: 404,
body: {
detail: 'User not found',
},
});
}
return {
status: 200,
body: user, // { id: string; name: string }
};
});That route answers exactly what it declared:
HTTP/1.1 200 OK
content-type: application/json
{
"id": "1",
"name": "Ada"
}HTTP/1.1 404 Not Found
content-type: application/problem+json
{
"type": "about:blank",
"title": "Not Found",
"status": 404,
"detail": "User not found"
}kizuna.config.ts collects your routes into the API you mount on your adapter. It validates every request against those schemas and answers failures as Problem Details.
The same declaration is what everything else is written from:
Runs on your framework
The adapter is one line of your config. Handlers get that framework's own typed request and response.
Express
req and res in every handler.
Fastify
request and reply in every handler.
Hono
c in every handler.
Next.js
request in every handler.
Running somewhere else? Write your own adapter.
Spec-driven
Kizuna follows the RFCs instead of inventing conventions, so your API behaves the way the web already expects. See every spec it follows.
Problem Details
Every error body is RFC 9457, sent as application/problem+json.
Refusals
401 carries the WWW-Authenticate challenge the identity declares.
Caching
Cache-Control, Vary and ETag from what the response declares.
Deprecation
Deprecation and Sunset headers from the route that announces them.
Errors are hard to get right, so Kizuna starts from Problem Details. Add your own fields and anything that speaks the standard still reads it:
responses: {
200: UserSchema,
429: ProblemDetailsSchema.extend({
retryAfter: z.int(),
}),
},Iterate without breaking your apps
Kizuna treats a breaking change as a decision you make, not one you discover in production. kizuna diff compares your routes against a git ref and reports what a change costs, from a renamed route down to a single field that became required. Breaking ones exit 1, so CI fails until someone labels the pull request.
BREAKINGGET /users/:id 200.email removedEasy to extend
Anything Kizuna ships, you can extend yourself with the same tools we used.
createAdapter
Serve on a runtime Kizuna has never heard of.
createGenerator
Walk your routes and write anything from them.
createPlugin
Add endpoints and helpers that reach every handler.
createJobTransport
Put your jobs on any queue or scheduler.
Start here
Quickstart
Declare a route, serve it, and call it from a typed client in 8 minutes.
Config
kizuna.config.ts names the framework you serve on and what the API is made of.
Routes
A method, a path, its schemas, and the handler that serves it.
Adapters
Express, Fastify, Hono, or the Next.js App Router.
Authentication
A credential you accept, and the guard that turns it into the caller.
CLI
Write your types and clients, print your routes, and diff an API against a git ref.