Skip to main content

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/openapi 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/openapi

Configuration

Create a zelt.config.ts file in your project root:

import { defineConfig } from '@zeltjs/openapi';

export default defineConfig({
controllers: ['./src/**/*.controller.ts'],
dist: './generated',
tsconfig: './tsconfig.json',
});

Generating AppType

pnpm zelt-openapi build

This generates <dist>/app.gen.ts containing the AppType.

Generated app.gen.ts

// THIS FILE IS GENERATED BY @zeltjs/openapi. DO NOT EDIT.
import type { Route, BuildAppType } from '@zeltjs/openapi';
import type { HelloController } from '../src/controllers/hello.controller';

export type AppType = BuildAppType<[
Route<'GET', '/hello/:name', typeof HelloController.prototype.greet>,
Route<'POST', '/hello', typeof HelloController.prototype.create>,
]>;

Using AppType

Type-Safe API Client

import { hc } from 'hono/client';
import type { AppType } from './generated/app.gen';

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

import { hc } from 'hono/client';
import { describe, it, expect } from 'vitest';
import { app } from './app';
import type { AppType } from './generated/app.gen';

describe('Hello API', () => {
const client = hc<AppType>('http://localhost', {
fetch: (input, init) => app.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

  1. Static Analysis — Analyzes controller method signatures at build time
  2. Type Extraction — Extracts request/response types from TypeScript types
  3. Code Generation — Generates AppType using type-level computation

The generated AppType maps your controller methods to Hono's route types, enabling the hc client to infer parameter and response types automatically.