К основному содержанию
скилл CX Codex

hono-rpc — скилл для Claude Code: типизированный клиент Hono

Скилл объясняет, как связать сервер и клиент Hono через типы TypeScript: экспорт AppType и клиент hc без кодогенерации.

Установить в Claude Code · Через Skills CLI (Vercel) Все способы и платформы ↓
npx skills add bobmatnyc/claude-mpm-skills --skill hono-rpc -a claude-code
Открыть на GitHub

Что делает hono-rpc

Скилл описывает схему Hono RPC: серверные маршруты объявляются цепочкой, из неё экспортируется тип AppType, а на клиенте hc<AppType> даёт методы с уже известными путями, телами запросов и типами ответов. Кодогенерация не нужна, всё держится на выводе типов TypeScript.

Внутри разобраны рабочие примеры: валидация через zValidator и Zod, параметры пути вида client.posts[':postId'].comments[':commentId'].$get, query, JSON-тело, form data и заголовки. Отдельный блок про InferResponseType и InferRequestType — типы ответов зависят от кода статуса, поэтому ветки 200 и 404 разбираются отдельно.

Есть подвох, о котором стоит знать заранее: маршруты нужно выстраивать одной цепочкой. Если писать app.get(...) и app.post(...) отдельными выражениями, типы теряются и в AppType часть путей просто не попадёт. Скилл помечен как не вызываемый пользователем напрямую — он подгружается сам, когда агент работает с Hono.

Какие задачи решает

  • получить типизированный клиент к API без OpenAPI и кодогенерации
  • проверить вызовы API на этапе компиляции, а не в рантайме
  • разделить типы между сервером и клиентом в монорепозитории
  • разобрать ответ по коду статуса: отдельно 200, отдельно 404
  • подключить Zod-валидацию к query, json, form и header

Примеры запросов агенту

  • Опиши серверные маршруты Hono цепочкой и экспортируй AppType для клиента
  • Сделай типизированный клиент через hc<AppType> и покажи вызов с параметром пути
  • Как вытащить тип JSON-тела запроса через InferRequestType для формы?

Как установить hono-rpc

1Через Skills CLI (Vercel)

npx skills add bobmatnyc/claude-mpm-skills --skill hono-rpc -a claude-code

2Вручную: положить файлы

~/.claude/skills/hono-rpc/SKILL.md

Папка скилла целиком; для одного проекта — .claude/skills/hono-rpc/

По шагам, с командами для каждой платформы: Как установить скилл в Claude Code: пять способов

Вопросы о hono-rpc

Как установить hono-rpc в Claude?

Выполните npx skills add bobmatnyc/claude-mpm-skills --skill hono-rpc -a claude-code. После установки файл скилла лежит в ~/.claude/skills/hono-rpc/SKILL.md.

Что делает hono-rpc?

Описывает, как получить сквозную типизацию между сервером и клиентом Hono: экспорт AppType на сервере и клиент hc на клиенте. Кодогенерация при этом не используется.

Почему часть маршрутов не попадает в AppType?

Скорее всего маршруты объявлены отдельными выражениями. Цепочка app.get(...).post(...) сохраняет типы, а разрозненные вызовы их теряют.

Исходный текст

SKILL.md · MIT
---
name: hono-rpc
description: Hono RPC - end-to-end type-safe API client generation with hc client and TypeScript inference
user-invocable: false
disable-model-invocation: true
skill_version: 1.0.0
updated_at: 2025-01-03T00:00:00Z
tags: [hono, rpc, type-safety, typescript, api-client, full-stack]
progressive_disclosure:
  entry_point:
    summary: "End-to-end type-safe API client with automatic TypeScript inference from server routes"
    when_to_use: "Building full-stack TypeScript apps where client needs type-safe API access"
    quick_start: "1. Export AppType from server 2. Import hc client 3. Use typed client methods"
  references: []
context_limit: 800
---

# Hono RPC - Type-Safe Client

## Overview

Hono RPC enables sharing API specifications between server and client through TypeScript's type system. Export your server's type, and the client automatically knows all routes, request shapes, and response types - no code generation required.

**Key Features**:
- Zero-codegen type-safe client
- Automatic TypeScript inference
- Works with Zod validators
- Status code-aware response types
- Supports path params, query, headers

## When to Use This Skill

Use Hono RPC when:
- Building full-stack TypeScript applications
- Need type-safe API consumption without OpenAPI/codegen
- Want compile-time validation of API calls
- Sharing types between client and server in monorepos

## Basic Setup

### Server Side

```typescript
// server/index.ts
import { Hono } from 'hono'
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'

const app = new Hono()

// Define routes with validation
const route = app
  .get('/users', async (c) => {
    const users = [{ id: '1', name: 'Alice' }]
    return c.json({ users })
  })
  .post(
    '/users',
    zValidator('json', z.object({
      name: z.string(),
      email: z.string().email()
    })),
    async (c) => {
      const data = c.req.valid('json')
      return c.json({ id: '1', ...data }, 201)
    }
  )
  .get('/users/:id', async (c) => {
    const id = c.req.param('id')
    return c.json({ id, name: 'Alice' })
  })

// Export type for client
export type AppType = typeof route

export default app
```

### Client Side

```typescript
// client/api.ts
import { hc } from 'hono/client'
import type { AppType } from '../server'

// Create typed client
const client = hc<AppType>('http://localhost:3000')

// All methods are type-safe!
async function examples() {
  // GET /users
  const usersRes = await client.users.$get()
  const { users } = await usersRes.json()
  // users: { id: string; name: string }[]

  // POST /users - body is typed
  const createRes = await client.users.$post({
    json: {
      name: 'Bob',
      email: '[email protected]'
    }
  })
  const created = await createRes.json()
  // created: { id: string; name: string; email: string }

  // GET /users/:id - params are typed
  const userRes = await client.users[':id'].$get({
    param: { id: '123' }
  })
  const user = await userRes.json()
  // user: { id: string; name: string }
}
```

## Route Chaining for Type Export

**Important**: Chain routes for proper type inference:

```typescript
// CORRECT: Chain all routes
const route = app
  .get('/a', handlerA)
  .post('/b', handlerB)
  .get('/c', handlerC)

export type AppType = typeof route

// WRONG: Separate statements lose type info
app.get('/a', handlerA)
app.post('/b', handlerB)  // Types lost!

export type AppType = typeof app  // Missing routes!
```

## Request Patterns

### Path Parameters

```typescript
// Server
const route = app.get('/posts/:postId/comments/:commentId', async (c) => {
  const { postId, commentId } = c.req.param()
  return c.json({ postId, commentId })
})

// Client
const res = await client.posts[':postId'].comments[':commentId'].$get({
  param: {
    postId: '1',
    commentId: '42'
  }
})
```

### Query Parameters

```typescript
// Server
const route = app.get(
  '/search',
  zValidator('query', z.object({
    q: z.string(),
    page: z.coerce.number().optional(),
    limit: z.coerce.number().optional()
  })),
  async (c) => {
    const { q, page, limit } = c.req.valid('query')
    return c.json({ query: q, page, limit })
  }
)

// Client
const res = await client.search.$get({
  query: {
    q: 'typescript',
    page: 1,
    limit: 20
  }
})
```

### JSON Body

```typescript
// Server
const route = app.post(
  '/posts',
  zValidator('json', z.object({
    title: z.string(),
    content: z.string(),
    tags: z.array(z.string()).optional()
  })),
  async (c) => {
    const data = c.req.valid('json')
    return c.json({ id: '1', ...data }, 201)
  }
)

// Client
const res = await client.posts.$post({
  json: {
    title: 'Hello World',
    content: 'My first post',
    tags: ['typescript', 'hono']
  }
})
```

### Form Data

```typescript
// Server
const route = app.post(
  '/upload',
  zValidator('form', z.object({
    file: z.instanceof(File),
    description: z.string().optional()
  })),
  async (c) => {
    const { file, description } = c.req.valid('form')
    return c.json({ filename: file.name })
  }
)

// Client
const formData = new FormData()
formData.append('file', file)
formData.append('description', 'My file')

const res = await client.upload.$post({
  form: formData
})
```

### Headers

```typescript
// Server
const route = app.get(
  '/protected',
  zValidator('header', z.object({
    authorization: z.string()
  })),
  async (c) => {
    return c.json({ authenticated: true })
  }
)

// Client
const res = await client.protected.$get({
  header: {
    authorization: 'Bearer token123'
  }
})
```

## Response Type Inference

### Status Code-Aware Types

```typescript
// Server
const route = app.get('/user', async (c) => {
  const user = await getUser()

  if (!user) {
    return c.json({ error: 'Not found' }, 404)
  }

  return c.json({ id: user.id, name: user.name }, 200)
})

// Client - use InferResponseType
import { InferResponseType } from 'hono/client'

type SuccessResponse = InferResponseType<typeof client.user.$get, 200>
// { id: string; name: string }

type ErrorResponse = InferResponseType<typeof client.user.$get, 404>
// { error: string }

// Handle different status codes
const res = await client.user.$get()

if (res.status === 200) {
  const data = await res.json()
  // data: { id: string; name: string }
} else if (res.status === 404) {
  const error = await res.json()
  // error: { error: string }
}
```

### Request Type Inference

```typescript
import { InferRequestType } from 'hono/client'

type CreateUserRequest = InferRequestType<typeof client.users.$post>['json']
// { name: string; email: string }

// Use for form validation, state management, etc.
const [formData, setFormData] = useState<CreateUserRequest>({
  name: '',
  email: ''
})
```

## Multi-File Route Organization

### Organize Routes

```typescript
// server/routes/users.ts
import { Hono } from 'hono'

export const users = new Hono()
  .get('/', async (c) => c.json({ users: [] }))
  .post('/', async (c) => c.json({ created: true }, 201))
  .get('/:id', async (c) => c.json({ id: c.req.param('id') }))

// server/routes/posts.ts
export const posts = new Hono()
  .get('/', async (c) => c.json({ posts: [] }))
  .post('/', async (c) => c.json({ created: true }, 201))

// server/index.ts
import { Hono } from 'hono'
import { users } from './routes/users'
import { posts } from './routes/posts'

const app = new Hono()

const route = app
  .route('/users', users)
  .route('/posts', posts)

export type AppType = typeof route
export default app
```

### Client Usage

```typescript
import { hc } from 'hono/client'
import type { AppType } from '../server'

const client = hc<AppType>('http://localhost:3000')

// Routes are nested
await client.users.$get()         // GET /users
await client.users[':id'].$get()  // GET /users/:id
await client.posts.$get()         // GET /posts
```

## Error Handling

### Handle Fetch Errors

```typescript
async function fetchUser(id: string) {
  try {
    const res = await client.users[':id'].$get({
      param: { id }
    })

    if (!res.ok) {
      const error = await res.json()
      throw new Error(error.message || 'Failed to fetch user')
    }

    return await res.json()
  } catch (error) {
    if (error instanceof TypeError) {
      // Network error
      throw new Error('Network error')
    }
    throw error
  }
}
```

### Type-Safe Error Responses

```typescript
// Server
const route = app.get('/resource', async (c) => {
  try {
    const data = await fetchData()
    return c.json({ success: true, data })
  } catch (e) {
    return c.json({ success: false, error: 'Failed' }, 500)
  }
})

// Client
type ApiResponse<T> =
  | { success: true; data: T }
  | { success: false; error: string }

const res = await client.resource.$get()
const result: ApiResponse<DataType> = await res.json()

if (result.success) {
  console.log(result.data)  // Typed!
} else {
  console.error(result.error)
}
```

## Configuration Options

### Custom Fetch

```typescript
const client = hc<AppType>('http://localhost:3000', {
  // Custom fetch (for testing, logging, etc.)
  fetch: async (input, init) => {
    console.log('Fetching:', input)
    return fetch(input, init)
  }
})
```

### Default Headers

```typescript
const client = hc<AppType>('http://localhost:3000', {
  headers: {
    'Authorization': 'Bearer token',
    'X-Custom-Header': 'value'
  }
})
```

### Dynamic Headers

```typescript
const getClient = (token: string) =>
  hc<AppType>('http://localhost:3000', {
    headers: () => ({
      'Authorization': `Bearer ${token}`
    })
  })

// Or with a function that returns headers
const client = hc<AppType>('http://localhost:3000', {
  headers: () => {
    const token = getAuthToken()
    return token ? { 'Authorization': `Bearer ${token}` } : {}
  }
})
```

## Best Practices

### 1. Enable Strict Mode

```json
// tsconfig.json
{
  "compilerOptions": {
    "strict": true  // Required for proper type inference!
  }
}
```

### 2. Use Explicit Status Codes

```typescript
// CORRECT: Explicit status enables type discrimination
return c.json({ data }, 200)
return c.json({ error: 'Not found' }, 404)

// AVOID: c.notFound() doesn't work well with RPC
return c.notFound()  // Response type is not properly inferred
```

### 3. Split Large Apps

```typescript
// For large apps, split routes to reduce IDE overhead
const v1 = new Hono()
  .route('/users', usersRoute)
  .route('/posts', postsRoute)

const v2 = new Hono()
  .route('/users', usersV2Route)

// Export separate types
export type V1Type = typeof v1
export type V2Type = typeof v2
```

### 4. Consistent Response Shapes

```typescript
// Define standard response wrapper
type ApiSuccess<T> = { ok: true; data: T }
type ApiError = { ok: false; error: string; code?: string }
type ApiResponse<T> = ApiSuccess<T> | ApiError

// Use consistently
const route = app.get('/users/:id', async (c) => {
  const user = await findUser(c.req.param('id'))

  if (!user) {
    return c.json({ ok: false, error: 'User not found' } as ApiError, 404)
  }

  return c.json({ ok: true, data: user } as ApiSuccess<User>, 200)
})
```

## Quick Reference

### Client Methods

| HTTP Method | Client Method |
|-------------|---------------|
| GET | `client.path.$get()` |
| POST | `client.path.$post()` |
| PUT | `client.path.$put()` |
| DELETE | `client.path.$delete()` |
| PATCH | `client.path.$patch()` |

### Request Options

```typescript
client.path.$method({
  param: { id: '1' },           // Path parameters
  query: { page: 1 },           // Query parameters
  json: { name: 'Alice' },      // JSON body
  form: formData,               // Form data
  header: { 'X-Custom': 'v' }   // Headers
})
```

### Type Utilities

```typescript
import { InferRequestType, InferResponseType } from 'hono/client'

// Extract request type
type ReqType = InferRequestType<typeof client.users.$post>

// Extract response type by status
type Res200 = InferResponseType<typeof client.users.$get, 200>
type Res404 = InferResponseType<typeof client.users.$get, 404>
```

## Related Skills

- **hono-core** - Framework fundamentals
- **hono-validation** - Request validation
- **typescript-core** - TypeScript patterns

---

**Version**: Hono 4.x
**Last Updated**: January 2025
**License**: MIT

© bobmatnyc · MIT · bobmatnyc/claude-mpm-skills

Похожие скиллы

Всё по теме «Бэкенд и API» →
typescript-pro VoltAgent Community Субагент помогает писать TypeScript со сложными типами, generic-ами и проверкой типобезопасности между frontend и backend. Пригодится для крупных типизированных модулей. 9 602установок hono-typescript Mindrally Задаёт агенту правила разработки API на Hono и TypeScript под Cloudflare Workers, Deno, Bun и Node.js. Пригодится, когда проект уже на Hono или только переезжает на него. CX Codex +1 819установок bun-hono-integration secondsky Скилл подсказывает агенту, как строить API на Hono под Bun: маршруты, middleware, валидация через Zod, RPC-клиент. Пригодится, когда проект уже крутится на Bun. CX Codex +1 452установок mastra-hono agent-skills Скилл для сборки ИИ-агентов, инструментов и воркфлоу на Mastra v1 Beta с API-серверами на Hono. Пригодится, когда пишете агентную логику на TypeScript. CX Codex +1 373установок hono-api-scaffolder jezweb Создаёт маршруты Hono для Cloudflare Workers с проверкой Zod, обработкой ошибок и документацией API. Нужен после подготовки каркаса проекта. CX Codex +1 1 600установок solana-rpc agiprolabs Скилл даёт агенту прямой доступ к блокчейну Solana по JSON-RPC: балансы, токены, транзакции, аккаунты программ. Пригодится, когда готовые API не отдают нужные данные. CX Codex +1 451установок

Из того же набора65

sveltekit Скилл подсказывает агенту, как устроен SvelteKit: файловый роутинг, load-функции, form actions и адаптеры для деплоя. CX Codex +1 441установок wordpress-advanced-architecture Скилл по продвинутой разработке под WordPress: свои REST-маршруты, команды WP-CLI, кэширование транзиентами и объектным кэшем. CX Codex +1 441установок digitalocean-management Скилл для Claude, Codex и Cursor, который помогает настроить мониторинг, проверки доступности и проекты в DigitalOcean. Пригодится, когда нужно собрать наблюдаемость и алерты по дроплетам и кластерам. CX Codex +1 438установок flask Скилл подкладывает агенту готовые шаблоны Flask: фабрика приложения, блюпринты, обработка запросов и ответов. Пригодится, когда пишешь REST API или микросервис на Python. CX Codex +1 437установок hono-middleware Скилл с паттернами middleware для Hono: создание, композиция, встроенные модули и порядок выполнения. Пригодится, когда в приложение нужно добавить авторизацию, CORS или логирование. CX Codex +1 431установок golang-cli-cobra-viper Скилл для сборки консольных утилит на Go: структура команд через Cobra и конфигурация из флагов, переменных окружения и файлов через Viper. CX Codex +1 425установок dependency-audit Скилл для ревизии зависимостей: находит устаревшие пакеты, уязвимости и мусор, который давно никто не импортирует. CX Codex +1 422установок wordpress-plugin-fundamentals Скилл для разработки плагинов WordPress на PHP 8.3+ с ООП, хуками, работой с базой и Settings API. Пригодится, когда агенту нужно собрать структуру плагина, а не писать её с нуля. CX Codex +1 421установок

Спонсоры проекта

Компании, которые помогают держать аналитику открытой