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:

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

GET /users/1
HTTP/1.1 200 OK
content-type: application/json

{
    "id": "1",
    "name": "Ada"
}
GET /users/999
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.

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.

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:

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

Easy to extend

Anything Kizuna ships, you can extend yourself with the same tools we used.

Start here

On this page