Upgrade to Pro — share decks privately, control downloads, hide ads and more …

実用!Hono RPC2026

Avatar for yodaka yodaka
April 22, 2026

実用!Hono RPC2026

Avatar for yodaka

yodaka

April 22, 2026

More Decks by yodaka

Other Decks in Programming

Transcript

  1. 🔥 LT — Hono RPC 実用! Hono RPC2026 TypeScript でAPI

    開発するなら知っておきたいRPC tRPC / oRPC / Hono RPC yodaka 2026.04.21
  2. ABOUT ME 自己紹介 ALBUM · yo_gen ▶ よだか yodaka →

    フリーランスWeb Developer → Next.js / Hono / TypeScript / Flutter / Vue.js → TSKaigiスタッフ → 最近は型パズルと音楽制作 —ギターロック好き必聴のアルバム作りました。聴い てね STAFF · TSKAIGI 2026 TSKaigi 2026 ベルサール羽田空港 2026.05.22—23 2 DAYS 実用!Hono RPC 2026 / yodaka 01 / 19
  3. AGENDA 今日はなす5つのこと 01 Hono RPCとは何か(さくっと) 02 今日の題材 — 予約アプリの構成 03

    RPCで型を渡す — Next.js 側 04 OpenAPIで型を渡す — Flutter 側 05 推しポイント 実用!Hono RPC 2026 / yodaka 03 / 19
  4. 00 — BEFORE RPC そもそもHonoとは Web標準ベースの、小さくて速いフレームワーク • ランタイム差異を吸収するポータビリティ Cloudflare Workers,

    Fastly Compute, Deno, Bun, AWS Lambda,... • Web標準 https://wintercg.org/ (WinterTC) • 軽量・高速 Hono x 402,820 ops/sec ±4.78% (80 runs sampled) hono/tiny preset is under 14kB. Zero dependencies. Web Standards only. • 豊富なミドルウェア、カスタマイズ性 CORS、CSRF、JWT、Logger… • RPCやミドルウェアを使った、フロントエンドとの型共有 import { Hono } from 'hono' const app = new Hono() app.get('/', c => c.text('Hello Hono!') ) app.get('/health', c => c.json({ ok: true }) ) export default app 実用!Hono RPC 2026 / yodaka 04 / 19
  5. 01 — HONO RPC Hono RPCとは Hono本体の、フロントエンドへの型共有の仕組み • Honoのルーターの型をそのままクライアントへ •

    hc<AppType>() で呼び出し • パス・リクエスト・レスポンスまで型がつく • コード生成なし・ランタイムへの影響ほぼゼロ // server import { Hono } from 'hono' const app = new Hono() .get('/posts/:id', c => c.json({ id: c.req.param('id') }) ) export type AppType = typeof app // client import { hc } from 'hono/client' const client = hc<AppType>(baseUrl) const res = await client.posts[':id'] .$get({ param: { id: '1' } }) 実用!Hono RPC 2026 / yodaka 05 / 19
  6. 02 — PROJECT 今日の題材は、ある予約アプリ 飲食店の店舗と来店者が使うシステムをひとつのHonoで捌く WEB Next.js 来店者向け ADMIN Next.js

    店舗管理画面 APP Flutter 来店者ネイティブ pnpm monorepo │ Hono on ECS (Node.js) │ 3 front-ends → 1 Hono 実用!Hono RPC 2026 / yodaka 06 / 19
  7. 02 — PROJECT 型の渡し方は、クライアントごとに2通り Hono ROUTER + TYPES Next.js (Web)

    来店者向け Next.js (Admin) 店舗向け Flutter ネイティブアプリ RPC (hc) RPC (hc) OpenAPI 実用!Hono RPC 2026 / yodaka 07 / 19
  8. 03 — RPC APIを定義する Standard Schema Validatorを使えば、ハンドラ内に型がつく BACKEND/SRC/ADMIN.TS import {

    Hono } from 'hono' import { sValidator } from '@hono/standard-validator' import { z } from 'zod' export const adminRouter = new Hono() .post( '/posts', sValidator('json', z.object({ title: z.string(), body: z.string(), })), (c) => { // ↓ title: string, body: string に推論される const { title, body } = c.req.valid('json') return c.json({ ok: true }) } ) 実用!Hono RPC 2026 / yodaka 08 / 19
  9. 03 — RPC sValidator(Standard Schema) v4.7.0〜: Zod / Valibot /

    ArkType を同じ記法で扱える共通バリデーター • 以前はZod Validatorで書いていた Zodにロックインされ、RPCとOpenAPIで書き味も分かれていた • v4.7.0で @hono/standard-validator 登場 Standard Schema という共通インターフェース経由で統一 • Zod / Valibot / ArkType を同じ sValidator で ライブラリ選定に縛られず、ハンドラ内の型も同じように推論 MIXING ZOD + VALIBOT import { sValidator } from '@hono/standard-validator' import { z } from 'zod' import * as v from 'valibot' app.get('/:slug', sValidator('param', v.object({ slug: v.string(), })), sValidator('query', z.object({ name: z.string(), })), (c) => { // ↓ どちらも型付きで取れる const { slug } = c.req.valid('param') const { name } = c.req.valid('query') return c.json({ slug, name }) } ) 実用!Hono RPC 2026 / yodaka 09 / 19
  10. 03 — RPC 型をエクスポートする ルーターの型をそのまま外に出すだけ BACKEND/SRC/INDEX.TS import { adminRouter, webRouter,

    spRouter } from './routers' const app = new Hono() .route('/sp', spRouter) .route('/web', webRouter) .route('/admin', adminRouter) // 👇 これがクライアントに渡る" 契約" export type AdminAppType = typeof adminRouter export type WebAppType = typeof webRouter pnpm workspace なので、フロントからは import type で拾うだけ。 実用!Hono RPC 2026 / yodaka 10 / 19
  11. 03 — RPC クライアントで型を受け取る hc に型を渡すだけで、型安全なAPIクライアントの出来上がり FRONTEND/LIB/CLIENT.TS import { hc

    } from 'hono/client' import type { AdminAppType } from 'backend/src' export const client = hc< AdminAppType >(baseUrl, { headers: { authorization: `Bearer ${token}` }, }) 実用!Hono RPC 2026 / yodaka 11 / 19
  12. 03 — RPC 呼び出しも、ぜんぶ型安全 FRONTEND/APP/POSTS/PAGE.TSX const res = await client.

    posts.$post ({ json: { // ↓ title/body がサジェスト、型が違うと怒られる title: 'Hello', body: 'Hono is a cool project', }, }) if (res.ok) { const data = await res.json() // ← これも型がつく } PATH パスがサジェストされる INPUT Body / Queryに型 OUTPUT レスポンスも推論 実用!Hono RPC 2026 / yodaka 12 / 19
  13. 03 — RPC SWR / TanStack Queryとも相性◎ parseResponse でレスポンス処理を、$path() でキャッシュキーを、それぞ

    れ型安全に • parseResponse でfetcherが一行に v4.9.0〜: ok判定 + json取り出しを内包、NGなら DetailedError をthrow • $path() をSWRのキャッシュキーに v4.12.0〜: $url()と違いパス文字列をそのまま返す(キー用途にぴったり) • パスパラメータもtype-safeに組み立て client.api.posts[':id'].$path({ param: { id } }) • hookの戻り値は InferResponseType で推論 引数側は InferRequestType で受け取れる FRONTEND/HOOKS/USEPOSTS.TS import { parseResponse, type, type InferResponseType } from 'hono/client' import { client } from '@/lib/client' import useSWR from 'swr' const $get = client.api.posts.$get type Posts = InferResponseType<typeof $get> export function usePosts() { return useSWR<Posts>( client.api.posts.$path() , // ← キャッシュキー () => parseResponse($get()), ) } 実用!Hono RPC 2026 / yodaka 13 / 19
  14. 04 — FLUTTER hono-openapiでルートに説明と型をのせる 素のHonoのまま、describeRoute と validator を挟むだけ SCHEMAS import

    { Hono } from 'hono' import { z } from 'zod' import { describeRoute, resolver, validator, } from 'hono-openapi' const paramsSchema = z.object({ id: z.string(), }) const postSchema = z.object({ id: z.string(), title: z.string().meta({ example: ' 予約完了', }), }) ROUTE export const posts = new Hono().get( '/posts/:id', describeRoute({ description: ' 投稿の取得', responses: { 200: { content: { 'application/json': { schema: resolver(postSchema), }, } }, }, }), validator('param', paramsSchema), (c) => { const { id } = c.req.valid('param') return c.json({ id, title: ' 予約完了' }) } ) 実用!Hono RPC 2026 / yodaka 15 / 19
  15. 04 — FLUTTER ルート定義と実装が、同じチェーンに並ぶ DESCRIBEROUTE + VALIDATOR + HANDLER .get(

    '/posts/:id', describeRoute({ responses: { 200: { content: { 'application/json': { schema: resolver( postSchema ), } }, } } }), validator('param', paramsSchema), (c) => { const { id } = c.req.valid('param') return c.json({ id, title: ' 予約完了' }) } ) ✔ 素のHonoのチェーンを保つのでRPCの型もその まま流れる ✔ validatorで c.req.valid() に 型がのる ✔ describeRouteで OpenAPIスキーマも同じ場所に 集約 実用!Hono RPC 2026 / yodaka 16 / 19
  16. 04 — FLUTTER FlutterクライアントをOpenAPIから生成 01 HonoがOpenAPI JSONをホスト app.get('/openapi', openAPIRouteHandler(app, {…}))

    02 JSON → YAMLへ変換スクリプト CI で実行、差分をPR 化 03 openapi-generator でDartクライ アント生成 Flutter はこのAPI を呼ぶだけ → Flutter側にも、ひと続きの型安全 実用!Hono RPC 2026 / yodaka 17 / 19
  17. 05 — REFLECTIONS Hono RPCの推しポイント 🔥 コード生成ゼロ ルーターの型をそのまま共有。tscが走ればOK。 🧩 Honoに乗せるだけ

    Web標準APIで、Node/Workers/Bun/Deno/Lambda で動く。 ⚡ 書く側の認知負荷が低い 普通にAPIを書くだけで、型がクライアントに届く。 🌐 OpenAPIと"両立"できる RPCが効かない相手(Flutterなど)にはOpenAPI で。 実用!Hono RPC 2026 / yodaka 18 / 19
  18. SUMMARY まとめ → Hono RPC は「Honoのルーター型をそのまま共有する」仕組み → TS同士なら、コード生成なしで型安全に呼び出せる → 相手がTSでない時は、Honoに生成させたOpenAPIを通して型安全に

    → 1つのHonoで、Web / Admin / ネイティブを面倒見られる 🔥 開発体験 × 型安全 × マルチクライアント — ぜんぶ欲しい人にHono RPC 実用!Hono RPC 2026 / yodaka 19 / 19