Claude Code × ElysiaJS:Eden型安全クライアントで作るBunネイティブAPI
はじめに
HonoでAPIを書いていると、ある壁にぶつかる瞬間がある。フロントエンドにHono RPCで型を共有しようとすると、レスポンスの成功ケースは型がつくが、エラーのステータスコード別の型推論が効かない。「400の場合と500の場合でレスポンスの型が変わる」というユースケースで、型安全性に妥協しなければならなくなる。
ElysiaJSはその問題に正面から向き合うBunネイティブのWebフレームワークだ。Eden Treatyというクライアントを使うと、ステータスコード別のエラー型まで含めたEnd-to-End型安全性が、コード生成なしで実現できる。Claude CodeとCLAUDE.mdを組み合わせると、このパターンを一貫して生成する環境が作れる。
ElysiaJSとは——Bunネイティブの型安全フレームワーク
ElysiaJSは「Ergonomic Framework for Humans」を掲げるBun専用のWebフレームワークだ。Bunのネイティブ機能(HTTPサーバーAPI・ファイルシステム・ホットリロード)を直接活用するため、他のランタイムを抽象化したフレームワークとは異なる密な統合が特徴になっている。
@elysiajs/nodeを使えばNode.jsでも動作するが、本来の設計はBun環境での最大パフォーマンスを前提にしている。
バリデーションはZodではなくTypeBoxを使う。TypeBoxはBun上でAOT(Ahead-of-Time)コンパイルされるため、Zodと比較して大幅に高速なバリデーションが実現されている(実際の数値はElysiaJS公式ドキュメントのベンチマーク記述で確認してほしい)。
HonoやtRPCとの違い——Edenが実現するEnd-to-End型安全性
Hono RPCとの比較
どちらもコード生成なしの型安全クライアントを持つが、型安全性の範囲が違う:
| 観点 | ElysiaJS Eden | Hono RPC |
|---|---|---|
| 型安全の範囲 | ステータスコード別のエラー型まで網羅 | 成功ケースのみ(エラー型は未対応) |
| クエリ/ボディのパース | 静的解析で自動パース | 手動でのパース関数呼び出しが必要 |
| 型推論の深さ | 大規模ルートでも安定 | 多数のルートで型エラーが出やすい |
| ランタイム | Bun-native(Node.jsも可) | マルチランタイム |
Honoが「エッジランタイム全対応」という汎用性を取るのに対し、ElysiaJSは「Bunに特化した型安全性の深さ」を取る設計思想だ。
tRPCとの比較
ElysiaJS公式に「tRPCからの移行ガイド」があるほど、tRPCに近い開発体験を提供している。
tRPCはRESTful APIではなく独自の抽象レイヤーを強いるが、ElysiaJSはRESTful標準を守りながらtRPCと同等の型安全性を実現する。既存のREST APIからの移行が容易な点が実践上の大きな違いだ。
Claude Code × ElysiaJS——CLAUDE.mdに何を書くか
Claude CodeがElysiaJSのパターンに沿ったコードを生成するために、CLAUDE.mdに以下を記述する:
## Tech Stack
- Runtime: Bun
- Framework: ElysiaJS 1.x
- Validation: ElysiaJS built-in TypeBox (t.Object, t.String, t.Number...)
- Type-safe client: Eden Treaty (@elysiajs/eden)
## ElysiaJS Conventions
- Use t.Object() for body/query/params/response validation (do NOT use Zod)
- Always export typeof app from server entry for Eden Treaty
- Use new Elysia().group() for route grouping instead of separate router files
- Prefer .onError() for centralized error handling
- Plugin pattern: new Elysia().use(myPlugin) for modular code
## Commands
- Start: bun run src/index.ts
- Dev: bun --hot run src/index.ts
- Test: bun test「Zodを使わずTypeBoxを使う」と明示することが重要だ。指定がなければClaude Codeは慣れ親しんだZodでバリデーションを書く可能性がある。do NOT use Zodという否定形の記述がClaude Codeに正確に伝わる。
また、export type App = typeof appをサーバーのエントリーポイントに必ず書くルールを定義しておくと、Eden Treatyのクライアント実装を依頼したときにClaude Codeが正確なパターンを生成できる。
実装ステップ——ルーティング・バリデーション・Edenクライアント
基本ルーティングとバリデーション
// src/index.ts
import { Elysia, t } from 'elysia'
const app = new Elysia()
.post('/sign-up', ({ body }) => {
return { message: Welcome, ${body.username} }
}, {
body: t.Object({
username: t.String({ minLength: 3 }),
password: t.String({ minLength: 8 }),
email: t.String({ format: 'email' })
}),
response: {
200: t.Object({ message: t.String() }),
400: t.Object({ error: t.String() })
}
})
.listen(3000)
export type App = typeof appresponseオブジェクトにステータスコード別の型を定義できる。これがEden Treatyのクライアント側でエラーハンドリングの型を確定させる仕組みの根拠になる。
Eden Treaty——コード生成なしのEnd-to-End型安全
サーバー側はtypeof appをexportするだけだ:
// src/index.ts(サーバー)
const app = new Elysia()
.get('/hi', () => 'Hi Elysia')
.post('/mirror', ({ body }) => body, {
body: t.Object({
id: t.Number(),
name: t.String()
})
})
.listen(3000)
export type App = typeof appクライアント側はその型を受け取るだけで、すべての型が自動推論される:
// src/client.ts(クライアント)
import { treaty } from '@elysiajs/eden'
import type { App } from './index'
const api = treaty<App>('localhost:3000')
// POSTリクエスト(body型もレスポンス型も推論済み)
const { data, error } = await api.mirror.post({
id: 1,
name: 'Taro'
})
// エラーハンドリングもステータスコード別に型安全
if (error) {
switch (error.status) {
case 400:
console.error('Bad Request:', error.value)
break
default:
throw error.value
}
}
console.log(data) // { id: number, name: string } として型確定error.statusで分岐するとき、各ケースでerror.valueの型がステータスコードに対応した型として解決される。HonoやtRPCでは実現が難しかった「エラー型のステータスコード別推論」がコード生成なしで動く。
インストール:
bun add @elysiajs/edenClaude Codeへの具体的な指示パターン
新しいエンドポイントを追加する際の依頼例:
src/index.ts のアプリに、以下のエンドポイントを追加してください。
POST /posts
- body: t.Object({ title: t.String({ minLength: 1 }), content: t.String() })
- response: { 201: t.Object({ id: t.String(), title: t.String() }) }
- 既存のPatternと同じ構造でEden Treatyが使えるように typeof app もexportしてください。CLAUDE.mdにElysiaJSの規約が書かれていれば、Claude Codeはt.Objectでバリデーションを書き、responseオブジェクトにステータスコード別の型を定義し、export type App = typeof appを末尾に追加する一連の流れを実行する。
エンジニアリングマネジメントの視点
ElysiaJSの採用判断は、主にチームのランタイム選択と型安全性への要求度によって変わる。
Bunをすでにプロジェクトのランタイムとしているチームや、Claude Code × Bunの組み合わせで開発している場合は、ElysiaJSの型安全性は自然な恩恵として受け取れる。既存のHono + Node.jsプロジェクトをElysiaJSに移行するコストは、Honoとの記法の類似性から比較的低い(公式の移行ガイドも整備されている)。
一方、マルチランタイム対応(Cloudflare Workers + Node.jsなど)が必要な場合はHonoの選択肢が依然として有力だ。ElysiaJSはBun環境で真価を発揮するため、チームのインフラ戦略と合わせて判断したい。
まとめ
ElysiaJSのEden Treatyは、「コード生成なしでステータスコード別のエラー型まで推論する」という点でHono RPCやtRPCと異なる立ち位置にある。
BunでAPIを作る環境があるなら、まずnew Elysia()で基本ルーティングを書き、export type App = typeof appをエントリーポイントに追加してからtreatyでクライアントを繋いでみてほしい。エラーハンドリングのswitch文で各ケースの型が確定する体験が、型安全性の深さを実感する最短の入口になる。

コメント