Adapters

Hono

Mount a Kizuna API on a Hono application.

@kizunajs/hono serves a Kizuna API from a Hono application. Hono runs on Cloudflare Workers, Deno, Bun, Node.js, and other runtimes.

Requires Hono >= 4.

pnpm add @kizunajs/hono@beta hono
bun add @kizunajs/hono@beta hono
npm install @kizunajs/hono@beta hono

The adapter

honoAdapter() is what you name in kizuna.config.ts. Naming it is what gives every handler on this API Hono's own Context, typed.

Declare a route

src/routes/users.ts
import { k } from '../k';

export const users = k.routes('users', {
    listUsers: k
        .route({
            method: 'GET',
            path: '/users',
            query: ListUsersQuerySchema,
            responses: {
                200: UserListSchema,
            },
        })
        .handler(async ({ query }) => ({
            status: 200,
            body: {
                users: await db.users.findMany({
                    skip: (query.page - 1) * query.limit,
                    take: query.limit,
                }),
                total: await db.users.count(),
            },
        })),
    getUser: k
        .route({
            method: 'GET',
            path: '/users/:id',
            responses: {
                200: UserSchema,
                404: ProblemDetailsSchema,
            },
        })
        .handler(async ({ params }) => {
            const user = await db.users.findById(params.id);
            if (!user) {
                return {
                    status: 404,
                    body: {
                        detail: 'Not found',
                    },
                };
            }
            return {
                status: 200,
                body: user,
            };
        }),
});

Name Hono in your config

kizuna.config.ts
import { defineConfig } from 'kizunajs';
import { honoAdapter } from '@kizunajs/hono';
import { users } from './src/routes/users';

export default defineConfig({
    adapter: honoAdapter(),
    routes: {
        users,
    },
});

Mount it

src/index.ts
import { Hono } from 'hono';
import kizuna from '../kizuna.config';

const app = new Hono();

kizuna.api.mount(app);

export default app;

Handler context

Each handler receives { params, query, body, headers, c }. The c is Hono's Context, useful when you need to read cookies, reach environment bindings, or use anything else Hono exposes.

src/routes/users.ts
.handler(async ({ params, c }) => {
    const locale = c.req.header('accept-language');
    return {
        status: 200,
        body: await db.users.findById(params.id),
    };
});

Environment bindings

On runtimes like Cloudflare Workers, bindings live on the Hono context as c.env.

src/routes/users.ts
getUser: k
    .route({
        method: 'GET',
        path: '/users/:id',
        responses: {
            200: UserSchema,
        },
    })
    .handler(async ({ params, c }) => ({
        status: 200,
        body: await c.env.DB.prepare('SELECT * FROM users WHERE id = ?').bind(params.id).first(),
    })),

Guards

A guard sits on the identity it authenticates, and receives the credential extracted and typed, alongside the native c. See the Authentication guide.

src/identities.ts
import { k } from './k';

export const user = k.identity
    .bearer({
        context: z.object({
            userId: z.string(),
        }),
    })
    .guard(async ({ bearer, deny, c }) => {
        const session = bearer ? await verifySession(bearer.token, c.env) : undefined;
        if (!session) {
            return deny({
                status: 401,
                body: {
                    detail: 'Unauthorized',
                },
            });
        }
        return {
            userId: session.userId,
        };
    });

Middleware

For request-scoped values handlers need, use request context. For other middleware, such as logging, rate limiting, and caching headers, use Hono's own app.use. Authentication belongs in guards.

Options

src/index.ts
kizuna.api.mount(app, {
    responseValidation: false,
});
OptionDefaultDescription
responseValidationfalseValidate handler return values against response schemas. Enable in development.
formatErrornoneReshape error (>= 400) response bytes for migrating clients. Most don't need it (use Problem Details extension members). See Migrating an existing API.

Streams

A stream response is returned as a ReadableStream body. The generator's signal is c.req.raw.signal, which the Node server aborts when the client goes away.

Reference

On this page