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 honobun add @kizunajs/hono@beta hononpm install @kizunajs/hono@beta honoThe 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
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
import { defineConfig } from 'kizunajs';
import { honoAdapter } from '@kizunajs/hono';
import { users } from './src/routes/users';
export default defineConfig({
adapter: honoAdapter(),
routes: {
users,
},
});Mount it
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.
.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.
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.
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
kizuna.api.mount(app, {
responseValidation: false,
});| Option | Default | Description |
|---|---|---|
responseValidation | false | Validate handler return values against response schemas. Enable in development. |
formatError | none | Reshape 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.