Hono Client
Zelt generates type-safe client types (AppType) for Hono's hc client — enabling fully type-safe API calls with IDE autocomplete.
Overview
The @zeltjs/hono-client package generates AppType from your controller signatures. This type integrates with Hono's hc client to provide:
- Full TypeScript inference for request parameters and response bodies
- IDE autocomplete for API endpoints
- Compile-time type checking for API calls
Installation
pnpm add @zeltjs/hono-client
Configuration (CLI Plugin)
Add honoClientPlugin to your zelt.config.ts:
export default defineConfig({
entry: './dist/app.js',
plugins: [
honoClientPlugin({
outDir: './generated',
output: 'app-type.ts',
}),
],
});
Plugin Options
| Option | Type | Default | Description |
|---|---|---|---|
entry | string | config.entry | Path to built app module |
outDir | string | './generated' | Output directory |
output | string | 'app-type.ts' | Output filename |
Generating AppType
Run zelt build to generate the type file:
pnpm zelt build
This generates <outDir>/<output> (default: generated/app-type.ts) containing the AppType.
Programmatic Generation
When your build is not driven by the Zelt CLI — an electron-vite pipeline, a custom build script, a monorepo task runner — use GeneratorService.generateFromApp() directly. It takes the http feature of your app and returns the generated type source as a string:
const generator = new GeneratorService();
const content = await generator.generateFromApp(app.http, { distDir: './dist' });
writeFileSync('./dist/app-type.generated.ts', content, 'utf-8');
Run this as a build step after your app modules are compiled — for example a script executed by node against the built output, or an electron-vite buildStart hook.
Generate Options
| Option | Type | Description |
|---|---|---|
distDir | string | Directory of the built app output; generated imports are resolved against it |
portable | boolean | Emit a self-contained type file with resolved literal types instead of imports into distDir |
tsconfig | string | Path to the project tsconfig (required when portable: true) |
projectRoot | string | Project root for path resolution (required when portable: true) |
Use portable: true when the generated file is consumed outside the server package — for example an Electron renderer or a separate frontend workspace that cannot import from the server's dist.
Generated app-type.ts
// THIS FILE IS GENERATED BY @zeltjs/hono-client. DO NOT EDIT.
export type AppType = BuildAppType<[
Route<'GET', '/hello/:name', () => { message: string }>,
Route<'POST', '/hello', (input: { name: string }) => { id: string }>,
]>;
Using AppType
Type-Safe API Client
const client = hc<AppType>('https://api.example.com');
// Fully typed - IDE autocomplete and type checking
const response = await client.hello[':name'].$get({
param: { name: 'world' },
});
if (response.ok) {
const data = await response.json();
// data is typed as { message: string }
console.log(data.message);
}
Testing with Type-Safe Client
describe('Hello API', () => {
const client = hc<AppType>('http://localhost', {
fetch: (input, init) => readyApp.http.fetch(new Request(input, init)),
});
it('should return greeting', async () => {
const res = await client.hello[':name'].$get({
param: { name: 'world' },
});
expect(res.status).toBe(200);
const body = await res.json();
expect(body.message).toBe('Hello, world!');
});
});
How It Works
- Metadata Extraction — Reads route metadata from your Zelt app at build time
- Type Generation — Generates
AppTypefrom the extracted route information - Client Integration — The generated type integrates with Hono's
hcclient
The generated AppType maps your controller routes to Hono's route types, enabling the hc client to infer parameter and response types automatically.
Mixed success and error replies
Return plain data for JSON 200 and use response() for an explicit status. The plugin preserves each status/body pair instead of wrapping the whole union as JSON 200. The server reply data does not depend on Hono; this package translates it to Hono's client schema.
import { response } from '@zeltjs/core';
function findUser(found: boolean, res = response()) {
if (!found) return res.status(404).json({ code: 'NOT_FOUND' });
return { id: 'user-1' };
}
A generated client sees status 200 | 404 and narrows res.json() to the matching body. JSON serialization is reflected in the client type, including Date values becoming strings. Redirects and empty replies do not declare a JSON body.
Unexpected exceptions and responses introduced by global middleware/error handlers are separate contracts; they are not inferred from the handler's return type.