はじめに
正直なところ、エッジで動くWebフレームワークを探すのって結構大変だったんですよ。
Cloudflare Workers使いたいけどExpress動かないし、Deno用のフレームワークは情報少ないし...そんな悩みを一発で解決してくれるのがHonoというフレームワークです。
HonoはGitHubで27,600スター以上を獲得している、Web標準に準拠した超軽量・超高速のWebフレームワーク。日本人の和田優さんが開発を始めたこともあって、日本語のドキュメントや情報が充実しているのも嬉しいポイントですね。
名前の「Hono」は日本語の「炎」から来ていて、その名の通り燃えるような速さを誇ります。
Honoとは
Honoは、Web標準APIのみを使用して構築された、小さくてシンプルで超高速なWebフレームワークです。バージョン4.10.7(2025年12月時点)がリリースされており、278人のコントリビューターによって活発に開発が続いています。
最大の特徴はマルチランタイム対応。同じコードがCloudflare Workers、Fastly Compute、Deno、Bun、AWS Lambda、Node.jsなど、あらゆる環境で動作します。
「一度書けばどこでも動く」という話。これがちゃんと実現できているフレームワークって、意外と少ないんですよね。
公式サイト: https://hono.dev GitHub: https://github.com/honojs/hono
特徴・メリット
1. 圧倒的な軽量さと速度
Honoは12kB未満(hono/tinyプリセット)という驚異的な軽さです。依存関係ゼロで、Web標準APIのみを使用しています。
そして速度。RegExpRouterという独自のルーターが線形ループを避ける設計になっていて、ルート数が増えても処理速度が落ちにくい。
Express: 約 15,000 req/sec
Fastify: 約 50,000 req/sec
Hono: 約 400,000 req/sec(環境による)
30代になって思うのは、軽いは正義ということ。特にエッジ環境ではコールドスタートの速さが体感に直結するので、この軽量さは大きなアドバンテージです。
2. マルチランタイム対応
これ、意外と他のフレームワークにはない強みなんですよ。
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.text('Hello Hono!'))
export default app
このコードが、Cloudflare Workers、Deno、Bun、Node.js、AWS Lambda、Fastly Computeなど、あらゆる環境でそのまま動きます。
プロジェクトの途中でデプロイ先を変更したくなっても、コードの書き直しがほぼ不要。これは運用面で非常に助かります。
3. ファーストクラスのTypeScript対応
Honoは最初からTypeScriptで書かれています。型推論が優秀で、IDEの補完がバッチリ効きます。
import { Hono } from 'hono'
const app = new Hono()
app.get('/user/:id', (c) => {
const id = c.req.param('id') // string型として推論される
const query = c.req.query('name') // string | undefined として推論される
return c.json({ id, name: query })
})
型安全なのに冗長な型定義が不要。このバランス感覚が素晴らしい。
4. 豊富なビルトインミドルウェア
基本的な機能は全部揃っています。
import { Hono } from 'hono'
import { cors } from 'hono/cors'
import { logger } from 'hono/logger'
import { basicAuth } from 'hono/basic-auth'
import { compress } from 'hono/compress'
import { etag } from 'hono/etag'
const app = new Hono()
app.use('*', logger())
app.use('*', cors())
app.use('/admin/*', basicAuth({ username: 'admin', password: 'secret' }))
サードパーティのパッケージをあれこれ入れなくても、標準で対応できることが多い。コスパ的にこれはかなりデカい。
5. JSXサポート
Reactみたいな書き方でHTMLを返せます。
import { Hono } from 'hono'
import { html } from 'hono/html'
const app = new Hono()
const Layout = (props: { children: any }) => html`
<!DOCTYPE html>
<html>
<head><title>Hono App</title></head>
<body>${props.children}</body>
</html>
`
app.get('/', (c) => {
return c.html(
<Layout>
<h1>Hello Hono!</h1>
</Layout>
)
})
APIサーバーだけでなく、シンプルなWebページを返すのにも使えます。
インストール方法
新規プロジェクトの作成(推奨)
npm create hono@latest my-app
対話形式でデプロイ先を選べます。
? Which template do you want to use?
aws-lambda
bun
cloudflare-pages
cloudflare-workers
deno
fastly
nextjs
nodejs
vercel
各ランタイム別のセットアップ
Cloudflare Workers
npm create hono@latest my-app
# テンプレートで cloudflare-workers を選択
cd my-app
npm install
npm run dev
Bun
bun create hono my-app
cd my-app
bun run dev
Deno
deno run -A npm:create-hono my-app
cd my-app
deno task start
Node.js
npm create hono@latest my-app
# テンプレートで nodejs を選択
cd my-app
npm install
npm run dev
時短になりますね。どの環境でも数分で開発を始められます。
基本的な使い方
Hello World
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.text('Hello Hono!'))
export default app
シンプルですね。Expressに慣れてる人なら、この書き心地の良さがわかると思います。
ルーティング
import { Hono } from 'hono'
const app = new Hono()
// 基本的なルーティング
app.get('/users', (c) => c.json([{ id: 1, name: 'User' }]))
app.post('/users', (c) => c.json({ message: 'Created' }, 201))
app.put('/users/:id', (c) => c.json({ message: 'Updated' }))
app.delete('/users/:id', (c) => c.json({ message: 'Deleted' }))
// パスパラメータ
app.get('/users/:id', (c) => {
const id = c.req.param('id')
return c.json({ id })
})
// ワイルドカード
app.get('/files/*', (c) => {
const path = c.req.param('*')
return c.text(`File: ${path}`)
})
// 正規表現
app.get('/post/:date{[0-9]+}/:title{[a-z]+}', (c) => {
const { date, title } = c.req.param()
return c.json({ date, title })
})
レスポンスの種類
const app = new Hono()
// テキスト
app.get('/text', (c) => c.text('Hello'))
// JSON
app.get('/json', (c) => c.json({ message: 'Hello' }))
// HTML
app.get('/html', (c) => c.html('<h1>Hello</h1>'))
// リダイレクト
app.get('/redirect', (c) => c.redirect('/'))
// ステータスコード
app.get('/not-found', (c) => c.notFound())
バリデーション(Zodとの連携)
import { Hono } from 'hono'
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'
const app = new Hono()
const userSchema = z.object({
name: z.string().min(1),
email: z.string().email(),
age: z.number().min(0).optional()
})
app.post(
'/users',
zValidator('json', userSchema),
(c) => {
const user = c.req.valid('json')
// userの型が自動的に推論される
return c.json({ success: true, data: user })
}
)
Zodと組み合わせれば、型安全なバリデーションが簡単に実現できます。
グループ化とネスト
import { Hono } from 'hono'
const app = new Hono()
// APIグループ
const api = new Hono()
api.get('/users', (c) => c.json([]))
api.get('/posts', (c) => c.json([]))
// v1としてマウント
app.route('/api/v1', api)
// /api/v1/users, /api/v1/posts でアクセス可能
大規模なアプリでもルーティングを整理しやすい設計になっています。
実践的なユースケース
1. REST APIサーバー
import { Hono } from 'hono'
import { cors } from 'hono/cors'
import { logger } from 'hono/logger'
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'
type Todo = {
id: number
title: string
completed: boolean
}
let todos: Todo[] = []
let nextId = 1
const app = new Hono()
// ミドルウェア
app.use('*', logger())
app.use('*', cors())
// スキーマ定義
const createTodoSchema = z.object({
title: z.string().min(1, 'タイトルは必須です')
})
const updateTodoSchema = z.object({
title: z.string().min(1).optional(),
completed: z.boolean().optional()
})
// CRUD操作
app.get('/api/todos', (c) => c.json(todos))
app.get('/api/todos/:id', (c) => {
const id = parseInt(c.req.param('id'))
const todo = todos.find(t => t.id === id)
if (!todo) return c.json({ error: 'Not found' }, 404)
return c.json(todo)
})
app.post(
'/api/todos',
zValidator('json', createTodoSchema),
(c) => {
const { title } = c.req.valid('json')
const todo: Todo = { id: nextId++, title, completed: false }
todos.push(todo)
return c.json(todo, 201)
}
)
app.patch(
'/api/todos/:id',
zValidator('json', updateTodoSchema),
(c) => {
const id = parseInt(c.req.param('id'))
const todo = todos.find(t => t.id === id)
if (!todo) return c.json({ error: 'Not found' }, 404)
const updates = c.req.valid('json')
Object.assign(todo, updates)
return c.json(todo)
}
)
app.delete('/api/todos/:id', (c) => {
const id = parseInt(c.req.param('id'))
const index = todos.findIndex(t => t.id === id)
if (index === -1) return c.json({ error: 'Not found' }, 404)
todos.splice(index, 1)
return c.json({ success: true })
})
export default app
2. Cloudflare Workers + 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'
).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 (?, ?)'
).bind(name, email).run()
return c.json({ id: result.meta.last_row_id }, 201)
})
export default app
Cloudflare D1との連携も型安全に書けます。
3. 認証付きAPI
import { Hono } from 'hono'
import { jwt } from 'hono/jwt'
import { sign } from 'hono/jwt'
const app = new Hono()
const JWT_SECRET = 'your-secret-key'
// ログイン
app.post('/login', async (c) => {
const { username, password } = await c.req.json()
// 実際にはDBで検証
if (username === 'admin' && password === 'password') {
const token = await sign({ username, exp: Math.floor(Date.now() / 1000) + 60 * 60 }, JWT_SECRET)
return c.json({ token })
}
return c.json({ error: 'Invalid credentials' }, 401)
})
// 保護されたルート
app.use('/api/*', jwt({ secret: JWT_SECRET }))
app.get('/api/profile', (c) => {
const payload = c.get('jwtPayload')
return c.json({ username: payload.username })
})
export default app
JWTの署名・検証もビルトインで対応しています。
4. ファイルアップロード
import { Hono } from 'hono'
const app = new Hono()
app.post('/upload', async (c) => {
const body = await c.req.parseBody()
const file = body['file']
if (file instanceof File) {
const arrayBuffer = await file.arrayBuffer()
// ファイルを処理(R2に保存など)
return c.json({
name: file.name,
size: file.size,
type: file.type
})
}
return c.json({ error: 'No file uploaded' }, 400)
})
export default app
RPC機能
個人的にはこれが一番の推しポイントです。HonoにはElysiaのEden TreatyのようなRPC機能があります。
// server.ts
import { Hono } from 'hono'
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'
const app = new Hono()
.get('/users', (c) => c.json([{ id: 1, name: 'easegis' }]))
.post(
'/users',
zValidator('json', z.object({ name: z.string() })),
(c) => {
const { name } = c.req.valid('json')
return c.json({ id: 2, name })
}
)
export type AppType = typeof app
export default app
// client.ts
import { hc } from 'hono/client'
import type { AppType } from './server'
const client = hc<AppType>('http://localhost:8787')
// 完全に型安全なAPIコール
const res = await client.users.$get()
const users = await res.json() // { id: number, name: string }[] と推論される
const newUser = await client.users.$post({
json: { name: 'New User' }
})
フロントエンドとバックエンドで型を共有できる。tRPCみたいなことがフレームワーク標準でできます。
注意点
いいことばかり書いてきましたが、注意点もあります。
1. Web標準前提の設計
Node.js固有のAPIに依存したライブラリとの相性が悪いことがあります。Web標準に準拠したライブラリを選ぶ必要があります。
2. エコシステムはまだ成長中
ExpressやFastifyと比べると、サードパーティのミドルウェアは少なめ。ただ、公式のミドルウェアで主要なユースケースはカバーされています。
3. 学習リソース
日本語の情報は充実していますが、英語圏の情報量ではまだExpressに及びません。とはいえ、公式ドキュメントが非常によくできているので、そこまで困ることはないと思います。
まとめ
正直なところ、Honoは「エッジファースト」な開発を考えているなら一択ですね。
以下の条件に当てはまるなら、試してみる価値は間違いなくあります:
- Cloudflare WorkersやDenoでAPIを動かしたい
- 軽量で高速なフレームワークが欲しい
- TypeScriptで型安全に開発したい
- 複数のランタイムで同じコードを動かしたい
- 日本発のOSSを応援したい
月間1,400万ダウンロード以上という数字が、その実力を証明しています。
個人的には、エッジ環境での開発ならHono、Bunに最適化されたパフォーマンスを求めるならElysia、という使い分けをしています。どちらも素晴らしいフレームワークです。
まずはCloudflare Workersでの小さなAPIから始めてみてください。その軽さと速さに驚くはずです。QOL上がること間違いなしです。
公式サイト: https://hono.dev GitHub: https://github.com/honojs/hono ドキュメント: https://hono.dev/docs