この記事の対象読者
- Node.jsまたはDenoでWebアプリケーションを開発した経験がある方
- Express.jsの基本的な使い方(ルーティング、ミドルウェア)を理解している方
- TypeScriptの基本構文(型定義、ジェネリクス)を知っている方
- エッジコンピューティングやサーバーレス環境に興味がある方
この記事で得られること
- Honoの設計思想と「Web Standards」という概念の理解
- Cloudflare Workers、Bun、Node.jsでHonoアプリを動かすための実践的なコード
- RPCとZodを組み合わせた型安全なAPIの構築方法
- 環境別の設定ファイルテンプレート(開発・テスト・本番)
この記事で扱わないこと
- Node.jsやnpmの基本的なインストール方法
- TypeScriptの環境構築
- Cloudflareアカウントの作成手順
- React/Vueなどフロントエンドフレームワークとの統合詳細
1. Honoとの出会い〜なぜ今、新しいフレームワークなのか〜
「Expressでいいじゃん、なんで新しいフレームワークが必要なの?」
正直に言うと、私も最初はそう思っていた。Express.jsは10年以上の歴史があり、npmのダウンロード数は週に3000万を超える。膨大なミドルウェアエコシステムがあり、困ったときはStack Overflowで答えが見つかる。何が不満なんだ、と。
しかし、Cloudflare Workersでサーバーレスアプリケーションを構築しようとしたとき、壁にぶつかった。Expressは動かない。Node.js専用のAPIに依存しているからだ。仕方なくitty-routerを使ってみたが、シンプルすぎて機能が足りない。Sunderというフレームワークも試したが、APIが好みに合わなかった。
そんなとき出会ったのがHonoだ。
Honoは日本語で「炎」を意味する。Cloudflareの「flare(炎)」にかけた命名だ。作者のyusukebe(和田裕介)氏は、まさに私と同じフラストレーションを抱えていた。そして、自分でフレームワークを作ってしまった。
"While trying to create my applications, I ended up creating my framework for them."
(アプリケーションを作ろうとしていたら、いつの間にかフレームワークを作っていた)— Yusuke Wada, The story of web framework Hono
この「車輪の再発明」が、結果的に現代のWeb開発に最適化された素晴らしいツールを生み出した。
Honoを一言で表現するなら「Web Standardsの上に構築された、どこでも動く爆速Webフレームワーク」だ。Cloudflare Workersだけでなく、Deno、Bun、Node.js、AWS Lambda、Vercel、Fastly Computeと、あらゆるJavaScriptランタイムで同じコードが動く。
ここまでで、Honoがなぜ生まれたのか、どんな問題を解決するのかがイメージできただろうか。次は、この技術の背景にある「Web Standards」という概念を押さえておこう。
2. 前提知識の確認
本題に入る前に、この記事で頻出する用語を整理しておく。
2.1 Web Standardsとは
Web Standards(Web標準)とは、ブラウザで使われるAPIの標準仕様のことだ。具体的には Request、Response、fetch、URL、Headersといったオブジェクトを指す。
従来のNode.jsは独自のAPIを持っていた。http.IncomingMessageやhttp.ServerResponseといったクラスだ。これらはNode.js固有のもので、他のランタイムでは動かない。
Web Standardsを採用するフレームワークは、ブラウザと同じAPIで動作する。つまり、一度書いたコードが複数の環境で動く「ポータビリティ」が得られる。
2.2 エッジコンピューティングとは
エッジコンピューティングとは、ユーザーに物理的に近い場所でコードを実行する仕組みだ。Cloudflare Workersは世界200以上の都市にサーバーを持ち、ユーザーの最寄りのサーバーでコードが動く。
従来の中央集権型サーバーでは、東京からアメリカのサーバーにリクエストを送ると往復100ms以上かかる。エッジでは数msで済む。レイテンシの大幅な削減が可能だ。
2.3 Contextオブジェクトとは
Honoでは、リクエストとレスポンスを扱うための「Context」オブジェクト(慣例的にcと呼ぶ)が、すべてのハンドラーとミドルウェアに渡される。
Expressのreqとresを一つにまとめたようなものと考えるとわかりやすい。c.reqでリクエスト情報にアクセスし、c.json()やc.text()でレスポンスを返す。
2.4 RPCとは
RPC(Remote Procedure Call)は、リモートサーバー上の関数をローカル関数のように呼び出す仕組みだ。HonoのRPC機能は、サーバーのAPI仕様をTypeScriptの型としてクライアントと共有できる。これにより、エンドツーエンドの型安全性が実現する。
これらの用語を押さえたところで、抽象的な概念を具体的なコードで見ていこう。
3. Honoが生まれた背景〜なぜExpressでは不十分だったのか〜
3.1 Express.jsの限界
Express.jsは2010年に登場した。当時のWeb開発はシンプルで、Node.jsサーバーは一つのマシンで動くモノリスだった。Expressはその時代に最適化されている。
しかし、現代のWeb開発環境は大きく変わった。
| 課題 | Expressの問題点 |
|---|---|
| マルチランタイム | Node.js専用APIに依存し、他環境で動かない |
| TypeScript | 型定義が後付けで、完全な型推論が困難 |
| エッジ対応 | コールドスタートが遅く、軽量性に欠ける |
| Web Standards | 独自のRequest/Responseオブジェクトを使用 |
Expressは「Node.jsのためのフレームワーク」として設計された。それは当時の正しい選択だったが、今日のマルチランタイム時代には足かせになっている。
3.2 Honoの設計思想
Honoは最初からCloudflare Workers向けに設計された。しかし、バージョン2からDenoとBunのサポートを追加した。この決断が成長の転機となった。
"If Hono had been targeted only at Cloudflare Workers, it might not have attracted as many users. By running on more runtimes, it gains more users, leading to the discovery of bugs and receiving more feedback, which ultimately leads to higher quality software."
(Honoが Cloudflare Workers専用だったら、これほど多くのユーザーを獲得できなかったかもしれない。より多くのランタイムで動くことで、より多くのユーザーが集まり、バグの発見とフィードバックが増え、最終的に高品質なソフトウェアにつながる)— Yusuke Wada, The story of web framework Hono
Honoの公式サイトは5つの特徴を掲げている。
| 特徴 | 説明 |
|---|---|
| Ultrafast | RegExpRouterは線形ループを使わない高速ルーター |
| Lightweight |
hono/tinyプリセットは12kB未満、依存関係ゼロ |
| Multi-runtime | 同じコードがあらゆるランタイムで動作 |
| Batteries Included | 組み込みミドルウェアが充実 |
| Delightful DX | クリーンなAPIと完璧なTypeScriptサポート |
(出典: Hono公式サイト および GitHub honojs/hono)
背景が理解できたところで、抽象的な概念から具体的なコードに移ろう。実際にHonoを動かしてみる。
4. Honoの基本概念〜3つのコアコンセプト〜
4.1 Honoアプリケーションのインスタンス化
HonoアプリケーションはHonoクラスをインスタンス化することから始まる。これはExpressのconst app = express()と同じ感覚だ。
import { Hono } from 'hono'
const app = new Hono()
このappオブジェクトにルートとミドルウェアを登録していく。
4.2 Contextオブジェクト(c)
Honoの心臓部はContextオブジェクトだ。すべてのハンドラー関数は第一引数にContextを受け取る。
app.get('/', (c) => {
// c.req: リクエスト情報
// c.json(): JSONレスポンスを返す
// c.text(): テキストレスポンスを返す
// c.html(): HTMLレスポンスを返す
return c.text('Hello Hono!')
})
Contextは以下の主要なプロパティとメソッドを持つ。
| プロパティ/メソッド | 用途 |
|---|---|
c.req |
リクエストオブジェクト(パラメータ、クエリ、ボディ取得) |
c.json(data, status?) |
JSONレスポンスを返す |
c.text(text, status?) |
プレーンテキストを返す |
c.html(html, status?) |
HTMLを返す |
c.header(name, value) |
レスポンスヘッダーを設定 |
c.set(key, value) |
ミドルウェア間でデータを共有 |
c.get(key) |
共有データを取得 |
4.3 ミドルウェアの仕組み
Honoのミドルウェアは、ハンドラーの前後に処理を挿入する仕組みだ。next()を呼ぶことで、次のミドルウェアまたはハンドラーに制御を渡す。
app.use(async (c, next) => {
console.log('Before handler')
await next()
console.log('After handler')
})
この「オニオン構造」はExpressと同じだが、HonoはPromiseベースで統一されている。next()がエラーをthrowしても、Honoがキャッチしてエラーハンドラーに渡すので、try-catchは不要だ。
基本概念を理解したところで、具体的な環境構築とコード実装に進もう。
5. 実際に使ってみよう〜環境別セットアップ〜
5.1 環境構築(3つのランタイム対応)
Honoは複数のランタイムで動作する。ここでは代表的な3つの環境でのセットアップを示す。
Cloudflare Workers(推奨)
# プロジェクト作成
npm create hono@latest my-app
# テンプレート選択時に「cloudflare-workers」を選ぶ
# 依存関係をインストール
cd my-app
npm install
Bun
# Bunでプロジェクト作成
bun create hono@latest my-app
# テンプレート選択時に「bun」を選ぶ
cd my-app
bun install
Node.js
# プロジェクト作成
npm create hono@latest my-app
# テンプレート選択時に「nodejs」を選ぶ
cd my-app
npm install
5.2 設定ファイルの準備
プロジェクトの設定ファイルを環境別に用意しておくことで、開発から本番までスムーズに移行できる。以下の3種類のテンプレートをそのままコピーして使える。
開発環境用(wrangler.toml)
# wrangler.toml - 開発環境用(このままコピーして使える)
# Cloudflare Workers向けの基本設定
name = "my-hono-app"
main = "src/index.ts"
compatibility_date = "2024-01-01"
# 開発用の設定
[vars]
ENVIRONMENT = "development"
LOG_LEVEL = "debug"
# ローカル開発時のポート設定
[dev]
port = 8787
local_protocol = "http"
本番環境用(wrangler.production.toml)
# wrangler.production.toml - 本番環境用(このままコピーして使える)
# 本番デプロイ時に使用: wrangler deploy -c wrangler.production.toml
name = "my-hono-app-production"
main = "src/index.ts"
compatibility_date = "2024-01-01"
minify = true
# 本番用の設定
[vars]
ENVIRONMENT = "production"
LOG_LEVEL = "error"
# カスタムドメインの設定(例)
# routes = [
# { pattern = "api.example.com/*", zone_id = "YOUR_ZONE_ID" }
# ]
# KVやD1などのバインディング(本番用ID)
# [[kv_namespaces]]
# binding = "MY_KV"
# id = "production-kv-namespace-id"
テスト環境用(wrangler.test.toml)
# wrangler.test.toml - テスト/CI環境用(このままコピーして使える)
# CI/CDパイプラインでの統合テスト向け設定
name = "my-hono-app-test"
main = "src/index.ts"
compatibility_date = "2024-01-01"
# テスト用の設定
[vars]
ENVIRONMENT = "test"
LOG_LEVEL = "info"
# テスト用のKVプレビュー
# [[kv_namespaces]]
# binding = "MY_KV"
# id = "test-kv-namespace-id"
# preview_id = "preview-kv-namespace-id"
Node.js用 package.json テンプレート
{
"name": "my-hono-app",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "tsx watch src/index.ts",
"start": "node dist/index.js",
"build": "tsc",
"test": "vitest"
},
"dependencies": {
"@hono/node-server": "^1.13.0",
"hono": "^4.6.0"
},
"devDependencies": {
"@types/node": "^20.0.0",
"tsx": "^4.0.0",
"typescript": "^5.0.0",
"vitest": "^2.0.0"
}
}
Bun用 package.json テンプレート
{
"name": "my-hono-app",
"version": "1.0.0",
"scripts": {
"dev": "bun run --hot src/index.ts",
"start": "bun run src/index.ts",
"test": "bun test"
},
"dependencies": {
"hono": "^4.6.0"
},
"devDependencies": {
"@types/bun": "latest"
}
}
5.3 基本的なAPIサーバーの実装
設定ファイルが準備できたら、実際にコードを書いていこう。以下は完全に動作するサンプルコードだ。
/**
* src/index.ts - Hono基本APIサーバー
*
* 使い方:
* - Cloudflare Workers: npm run dev
* - Bun: bun run dev
* - Node.js: npm run dev
*/
import { Hono } from 'hono'
import { cors } from 'hono/cors'
import { logger } from 'hono/logger'
import { prettyJSON } from 'hono/pretty-json'
// 型定義(環境変数やバインディング用)
type Bindings = {
ENVIRONMENT: string
// KVやD1を使う場合はここに追加
// MY_KV: KVNamespace
}
// アプリケーション初期化
const app = new Hono<{ Bindings: Bindings }>()
// グローバルミドルウェア
app.use('*', logger())
app.use('*', prettyJSON())
app.use('*', cors({
origin: '*',
allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
}))
// ルートエンドポイント
app.get('/', (c) => {
return c.json({
message: 'Hello Hono!',
environment: c.env?.ENVIRONMENT || 'unknown',
timestamp: new Date().toISOString(),
})
})
// ユーザーAPI
app.get('/api/users', (c) => {
const users = [
{ id: 1, name: 'Alice', email: 'alice@example.com' },
{ id: 2, name: 'Bob', email: 'bob@example.com' },
]
return c.json(users)
})
app.get('/api/users/:id', (c) => {
const id = c.req.param('id')
const page = c.req.query('page') || '1'
// レスポンスヘッダーを設定
c.header('X-Request-ID', crypto.randomUUID())
return c.json({
id: parseInt(id),
name: `User ${id}`,
page: parseInt(page),
})
})
app.post('/api/users', async (c) => {
try {
const body = await c.req.json()
// バリデーション(後のセクションでZodを使った方法を解説)
if (!body.name || !body.email) {
return c.json({ error: 'name and email are required' }, 400)
}
return c.json({
id: Math.floor(Math.random() * 1000),
name: body.name,
email: body.email,
createdAt: new Date().toISOString(),
}, 201)
} catch (error) {
return c.json({ error: 'Invalid JSON body' }, 400)
}
})
// エラーハンドリング
app.onError((err, c) => {
console.error('Error:', err)
return c.json({
error: 'Internal Server Error',
message: err.message,
}, 500)
})
// 404ハンドリング
app.notFound((c) => {
return c.json({
error: 'Not Found',
path: c.req.path,
}, 404)
})
// エクスポート(ランタイムに応じて自動的に処理される)
export default app
Node.jsで動かす場合は、エントリーポイントにserve関数を追加する。
/**
* src/index.ts - Node.js用エントリーポイント
*/
import { serve } from '@hono/node-server'
import app from './app' // 上記のappを別ファイルに分離した場合
const port = Number(process.env.PORT) || 3000
console.log(`Server is running on http://localhost:${port}`)
serve({
fetch: app.fetch,
port,
})
5.4 実行結果
上記のコードを実行すると、以下のような出力が得られる。
$ npm run dev
> my-hono-app@1.0.0 dev
> wrangler dev
⛅️ wrangler 3.99.0
-------------------
⎔ Starting local server...
[mf:inf] Ready on http://localhost:8787
# 別ターミナルでAPIをテスト
$ curl http://localhost:8787/
{
"message": "Hello Hono!",
"environment": "development",
"timestamp": "2024-01-15T10:30:00.000Z"
}
$ curl http://localhost:8787/api/users
[
{"id":1,"name":"Alice","email":"alice@example.com"},
{"id":2,"name":"Bob","email":"bob@example.com"}
]
$ curl -X POST http://localhost:8787/api/users \
-H "Content-Type: application/json" \
-d '{"name":"Charlie","email":"charlie@example.com"}'
{
"id": 742,
"name": "Charlie",
"email": "charlie@example.com",
"createdAt": "2024-01-15T10:31:00.000Z"
}
5.5 よくあるエラーと対処法
Honoを使い始めると、いくつかのエラーに遭遇することがある。私自身がハマったポイントも含めて、対処法をまとめた。
| エラー | 原因 | 対処法 |
|---|---|---|
TypeError: app.fetch is not a function |
エクスポート方法の誤り |
export default appを確認。Node.jsの場合はserve({ fetch: app.fetch, port })を使う |
Cannot find module 'hono' |
依存関係未インストール |
npm install honoを実行。Node.jsなら@hono/node-serverも必要 |
c.env is undefined |
環境変数未設定 |
wrangler.tomlの[vars]セクションを確認。ローカルは.dev.varsファイルも使える |
CORS error |
CORSミドルウェア未設定 |
import { cors } from 'hono/cors'してapp.use('*', cors())を追加 |
ValidationError (Zod使用時) |
リクエストデータが不正 | スキーマ定義を確認。エラーメッセージに詳細が出力される |
wrangler: command not found |
Wrangler未インストール |
npm install -g wranglerまたはnpx wranglerを使う |
基本的な使い方をマスターしたので、次はHonoの強力な機能であるRPCと型安全なバリデーションを見ていこう。
6. 型安全なAPI開発〜RPCとZodの活用〜
6.1 Zodによるバリデーション
Zodは TypeScript ファーストのスキーマバリデーションライブラリだ。Honoには公式のZodバリデーターミドルウェアが用意されている。
# Zodバリデーターをインストール
npm install zod @hono/zod-validator
/**
* src/routes/users.ts - Zodを使った型安全なAPI
*/
import { Hono } from 'hono'
import { z } from 'zod'
import { zValidator } from '@hono/zod-validator'
const app = new Hono()
// リクエストスキーマの定義
const createUserSchema = z.object({
name: z.string().min(1, 'Name is required').max(100),
email: z.string().email('Invalid email format'),
age: z.number().int().min(0).max(150).optional(),
})
const userIdSchema = z.object({
id: z.string().regex(/^\d+$/, 'ID must be a number'),
})
const querySchema = z.object({
page: z.string().regex(/^\d+$/).optional().default('1'),
limit: z.string().regex(/^\d+$/).optional().default('10'),
})
// バリデーション付きルート
app.post(
'/users',
zValidator('json', createUserSchema),
(c) => {
// validated は型推論される: { name: string, email: string, age?: number }
const validated = c.req.valid('json')
return c.json({
id: Math.floor(Math.random() * 1000),
...validated,
createdAt: new Date().toISOString(),
}, 201)
}
)
app.get(
'/users/:id',
zValidator('param', userIdSchema),
zValidator('query', querySchema),
(c) => {
const { id } = c.req.valid('param')
const { page, limit } = c.req.valid('query')
return c.json({
id: parseInt(id),
page: parseInt(page),
limit: parseInt(limit),
})
}
)
export default app
6.2 RPCによるエンドツーエンド型安全性
HonoのRPC機能は、サーバーのAPI仕様をTypeScriptの型としてクライアントと共有する。これにより、APIの変更がコンパイル時に検出される。
/**
* src/server.ts - RPC対応サーバー
*/
import { Hono } from 'hono'
import { z } from 'zod'
import { zValidator } from '@hono/zod-validator'
const app = new Hono()
// スキーマ定義
const postSchema = z.object({
title: z.string().min(1),
body: z.string().min(1),
})
// ルートを変数に格納(型推論のため)
const route = app
.get('/api/posts', (c) => {
return c.json([
{ id: 1, title: 'Hello', body: 'World' },
{ id: 2, title: 'Hono', body: 'is awesome' },
])
})
.post(
'/api/posts',
zValidator('json', postSchema),
(c) => {
const data = c.req.valid('json')
return c.json({
id: Date.now(),
...data,
createdAt: new Date().toISOString(),
}, 201)
}
)
.get('/api/posts/:id', (c) => {
const id = c.req.param('id')
return c.json({ id: parseInt(id), title: 'Post', body: 'Content' })
})
// 型をエクスポート
export type AppType = typeof route
export default app
/**
* src/client.ts - 型安全なクライアント
*/
import { hc } from 'hono/client'
import type { AppType } from './server'
// クライアント作成
const client = hc<AppType>('http://localhost:8787')
async function main() {
// 型補完が効く!
const posts = await client.api.posts.$get()
const data = await posts.json()
console.log(data) // { id: number, title: string, body: string }[]
// POSTも型安全
const newPost = await client.api.posts.$post({
json: {
title: 'New Post',
body: 'This is the content',
},
})
const created = await newPost.json()
console.log(created) // { id: number, title: string, body: string, createdAt: string }
// パラメータも型チェック
const post = await client.api.posts[':id'].$get({
param: { id: '1' },
})
const postData = await post.json()
console.log(postData)
}
main()
RPCの真価は、サーバーの型がクライアントに伝播することだ。もしサーバー側でtitleをnameに変更したら、クライアント側でコンパイルエラーが発生する。APIドキュメントを見なくても、型システムが仕様を教えてくれる。
型安全なAPIの作り方がわかったところで、実際のユースケースに当てはめていこう。
7. ユースケース別ガイド
7.1 ユースケース1: シンプルなREST APIサーバー
想定読者: バックエンドAPIを素早く構築したい開発者
推奨構成: Bun + Hono(最速の組み合わせ)
特徴: 最小限の設定で高速なAPIサーバーを立ち上げられる
/**
* シンプルREST API - Bunで動作
* 起動: bun run --hot src/index.ts
*/
import { Hono } from 'hono'
const app = new Hono()
// インメモリデータストア
let todos: { id: number; text: string; done: boolean }[] = []
let nextId = 1
app.get('/todos', (c) => c.json(todos))
app.post('/todos', async (c) => {
const { text } = await c.req.json()
const todo = { id: nextId++, text, done: false }
todos.push(todo)
return c.json(todo, 201)
})
app.put('/todos/:id', async (c) => {
const id = parseInt(c.req.param('id'))
const { done } = await c.req.json()
const todo = todos.find((t) => t.id === id)
if (!todo) return c.json({ error: 'Not found' }, 404)
todo.done = done
return c.json(todo)
})
app.delete('/todos/:id', (c) => {
const id = parseInt(c.req.param('id'))
todos = todos.filter((t) => t.id !== id)
return c.json({ success: true })
})
export default app
7.2 ユースケース2: Cloudflare Workers + D1データベース
想定読者: サーバーレス環境でSQLデータベースを使いたい開発者
推奨構成: Cloudflare Workers + Hono + D1
特徴: エッジでSQLiteが動く。コールドスタートが速い
/**
* Cloudflare D1を使ったAPI
*
* 事前準備:
* 1. wrangler d1 create my-database
* 2. wrangler.tomlにD1バインディングを追加
*/
import { Hono } from 'hono'
type Bindings = {
DB: D1Database
}
const app = new Hono<{ Bindings: Bindings }>()
// ユーザー一覧取得
app.get('/users', async (c) => {
const { results } = await c.env.DB.prepare(
'SELECT * FROM users ORDER BY created_at DESC'
).all()
return c.json(results)
})
// ユーザー作成
app.post('/users', async (c) => {
const { name, email } = await c.req.json()
const result = await c.env.DB.prepare(
'INSERT INTO users (name, email) VALUES (?, ?) RETURNING *'
)
.bind(name, email)
.first()
return c.json(result, 201)
})
// ユーザー取得
app.get('/users/:id', async (c) => {
const id = c.req.param('id')
const user = await c.env.DB.prepare(
'SELECT * FROM users WHERE id = ?'
)
.bind(id)
.first()
if (!user) {
return c.json({ error: 'User not found' }, 404)
}
return c.json(user)
})
export default app
# wrangler.toml - D1バインディング設定
[[d1_databases]]
binding = "DB"
database_name = "my-database"
database_id = "your-database-id-here"
7.3 ユースケース3: フルスタックアプリケーション(JSX + API)
想定読者: ReactなしでSSRアプリを作りたい開発者
推奨構成: Cloudflare Pages + HonoX
特徴: APIとUIを同じプロジェクトで管理。JSX対応
/**
* フルスタックHonoアプリ - JSXでUI描画
*
* tsconfig.jsonに以下を追加:
* "jsx": "react-jsx",
* "jsxImportSource": "hono/jsx"
*/
import { Hono } from 'hono'
import type { FC } from 'hono/jsx'
const app = new Hono()
// レイアウトコンポーネント
const Layout: FC<{ title: string }> = (props) => {
return (
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{props.title}</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-100 min-h-screen">
<nav class="bg-blue-600 text-white p-4">
<h1 class="text-xl font-bold">My Hono App</h1>
</nav>
<main class="container mx-auto p-4">
{props.children}
</main>
</body>
</html>
)
}
// ホームページ
app.get('/', (c) => {
return c.html(
<Layout title="Home">
<div class="bg-white rounded-lg shadow p-6">
<h2 class="text-2xl font-bold mb-4">Welcome to Hono!</h2>
<p class="text-gray-600">
This is a full-stack application built with Hono and JSX.
</p>
<a
href="/users"
class="inline-block mt-4 bg-blue-500 text-white px-4 py-2 rounded"
>
View Users
</a>
</div>
</Layout>
)
})
// ユーザー一覧ページ
const users = [
{ id: 1, name: 'Alice', role: 'Admin' },
{ id: 2, name: 'Bob', role: 'User' },
{ id: 3, name: 'Charlie', role: 'User' },
]
app.get('/users', (c) => {
return c.html(
<Layout title="Users">
<div class="bg-white rounded-lg shadow">
<table class="w-full">
<thead class="bg-gray-50">
<tr>
<th class="px-6 py-3 text-left">ID</th>
<th class="px-6 py-3 text-left">Name</th>
<th class="px-6 py-3 text-left">Role</th>
</tr>
</thead>
<tbody>
{users.map((user) => (
<tr class="border-t">
<td class="px-6 py-4">{user.id}</td>
<td class="px-6 py-4">{user.name}</td>
<td class="px-6 py-4">{user.role}</td>
</tr>
))}
</tbody>
</table>
</div>
</Layout>
)
})
// APIエンドポイント
app.get('/api/users', (c) => c.json(users))
export default app
ユースケースごとの実装パターンを把握できた。次は、この記事を読んだ後の学習パスを確認しよう。
8. 学習ロードマップ
この記事を読んだ後、次のステップとして以下をおすすめする。
初級者向け(まずはここから)
-
公式Getting Startedを試す
- Hono Getting Started
- 各ランタイム(Bun、Cloudflare Workers、Node.js)のテンプレートを動かしてみる
-
組み込みミドルウェアを使う
- Built-in Middleware一覧
-
cors,logger,basicAuth,jwtなどを試す
-
Honoの公式Examplesを動かす
- honojs/examples
- 実際のユースケースを手を動かして学ぶ
中級者向け(実践に進む)
-
Zod OpenAPIでドキュメント自動生成
- @hono/zod-openapi
- スキーマからSwagger UIを自動生成
-
HonoXでフルスタック開発
- HonoX GitHub
- ファイルベースルーティングとVite統合
-
Cloudflare D1/KV/R2との統合
- Cloudflare Bindings
- エッジでのデータ永続化を学ぶ
上級者向け(さらに深く)
-
カスタムミドルウェアの作成
- Middleware Guide
- 認証、レート制限、キャッシュなど
-
パフォーマンスチューニング
- Benchmarks
- RegExpRouterの仕組みを理解する
-
Honoへのコントリビュート
- honojs/middleware
- サードパーティミドルウェアの提案・開発
9. まとめ
この記事では、Honoについて以下を解説した。
- Honoとは何か: Web Standardsの上に構築された、どこでも動く爆速Webフレームワーク
- なぜ生まれたか: Expressの限界(マルチランタイム非対応、エッジ非対応)を解決するため
- 基本概念: Honoインスタンス、Contextオブジェクト、ミドルウェアの仕組み
- 実践: 環境構築、設定ファイル、基本的なAPIサーバー実装
- 型安全性: ZodバリデーションとRPCによるエンドツーエンド型推論
- ユースケース: REST API、Cloudflare D1連携、フルスタックJSXアプリ
私の所感
正直に言うと、Honoを使い始めてからExpressに戻れなくなった。
理由は「書き心地」だ。TypeScriptの型推論が完璧に効く。Contextオブジェクトの設計がシンプルで直感的。ミドルウェアの登録も、ルートの定義も、すべてがスムーズに流れる。
そして何より、同じコードがCloudflare Workers、Bun、Node.jsで動くという安心感。プロジェクトの将来性を考えたとき、特定のランタイムにロックインされないのは大きなメリットだ。
Expressは偉大なフレームワークだ。10年以上にわたってNode.jsエコシステムを支えてきた。しかし、時代は変わった。エッジコンピューティング、サーバーレス、マルチランタイム。Honoは、その変化に適応するために生まれた。
もしあなたが新しいプロジェクトを始めるなら、Honoを選択肢に入れてみてほしい。最初のHello Hono!を動かした瞬間、きっと私と同じ興奮を味わえるはずだ。
参考文献
- Hono 公式サイト
- GitHub honojs/hono - GitHub Star 28,000以上
- The story of web framework Hono, from the creator of Hono - Cloudflare公式ブログ(2024年10月)
- Hono RPC Documentation
- Hono Validation Documentation
- @hono/zod-validator - npm
- Cloudflare Workers Documentation - Hono
- HonoX - Hono based meta framework