はじめに
こんにちは!最近APIやエッジ環境の開発で急速に人気を集めている Hono について、実務で使うと便利なTipsをまとめました。
Honoは Cloudflare Workers、Bun、Deno、Node.js、AWS Lambda など あらゆるJavaScriptランタイム で動作する、Web標準ベースの超軽量Webフレームワークです。ExpressライクなAPIで学習コストが低く、TypeScriptとの相性も抜群です。
この記事では、私が実際にHonoで開発する中で「これは知っておくと得する!」と感じた10個のTipsを紹介します。
1. hono/tiny でバンドルサイズを最小化する
Cloudflare Workersなどのエッジ環境では、バンドルサイズがコールドスタート性能に直結します。ルーターを使い分けることで最適化できます。
// 通常(RegExpRouter + SmartRouter)
import { Hono } from 'hono'
// 超軽量版(TrieRouterのみ)
import { Hono } from 'hono/tiny'
const app = new Hono()
hono/tiny は数KB削減できるので、シンプルなAPIならこちらがおすすめです。
2. Zod Validator で型安全なバリデーションを実装
@hono/zod-validator を使うと、リクエストのバリデーションと型推論が同時にできます。
import { Hono } from 'hono'
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'
const app = new Hono()
const schema = z.object({
name: z.string().min(1),
age: z.number().int().positive(),
})
app.post('/users', zValidator('json', schema), (c) => {
const { name, age } = c.req.valid('json') // 型が完全に推論される!
return c.json({ name, age })
})
c.req.valid('json') の返り値が自動的に型付けされるのが最高です。
3. RPCモードでフロントエンドと型を共有する
Honoの最強機能の一つが RPCモード です。バックエンドの型定義をそのままフロントエンドで使えます。
// server.ts
const route = app
.post('/users', zValidator('json', schema), (c) => {
return c.json({ id: 1, ...c.req.valid('json') })
})
export type AppType = typeof route
// client.ts
import { hc } from 'hono/client'
import type { AppType } from './server'
const client = hc<AppType>('http://localhost:8787')
const res = await client.users.$post({
json: { name: 'Taro', age: 25 } // 型チェックされる
})
const data = await res.json() // レスポンスも型付き!
tRPCのような体験が、追加ライブラリほぼゼロで得られます。
4. ミドルウェアを賢く使う
Honoの組み込みミドルウェアは強力です。よく使うものをまとめておきます。
import { Hono } from 'hono'
import { logger } from 'hono/logger'
import { cors } from 'hono/cors'
import { secureHeaders } from 'hono/secure-headers'
import { cache } from 'hono/cache'
const app = new Hono()
app.use('*', logger())
app.use('*', secureHeaders())
app.use('/api/*', cors({
origin: ['https://example.com'],
credentials: true,
}))
// Cloudflare Workers環境でのキャッシュ
app.get(
'/heavy',
cache({ cacheName: 'my-app', cacheControl: 'max-age=3600' }),
async (c) => c.json({ data: 'expensive computation' })
)
5. c.env で環境変数・バインディングを型安全に扱う
Cloudflare Workersでは、環境変数やD1、KV、R2などのバインディングを型定義しておくと開発体験が飛躍的に向上します。
type Bindings = {
DB: D1Database
KV: KVNamespace
API_KEY: string
}
const app = new Hono<{ Bindings: Bindings }>()
app.get('/users/:id', async (c) => {
const id = c.req.param('id')
const { results } = await c.env.DB
.prepare('SELECT * FROM users WHERE id = ?')
.bind(id)
.all()
return c.json(results)
})
6. c.set() / c.get() でリクエスト間の値を受け渡す
認証ミドルウェアからハンドラーにユーザー情報を渡す時に便利です。Variables 型で型付けしましょう。
type Variables = {
userId: string
}
const app = new Hono<{ Variables: Variables }>()
app.use('/api/*', async (c, next) => {
const token = c.req.header('Authorization')
const userId = await verifyToken(token) // 検証処理
c.set('userId', userId)
await next()
})
app.get('/api/me', (c) => {
const userId = c.get('userId') // 型推論される
return c.json({ userId })
})
7. ルートをファイル分割して見通しをよくする
大規模になってきたら、ルートを機能ごとに分割しましょう。
// routes/users.ts
import { Hono } from 'hono'
const users = new Hono()
users.get('/', (c) => c.json({ users: [] }))
users.get('/:id', (c) => c.json({ id: c.req.param('id') }))
export default users
// index.ts
import { Hono } from 'hono'
import users from './routes/users'
import posts from './routes/posts'
const app = new Hono()
app.route('/users', users)
app.route('/posts', posts)
export default app
注意:RPCで型推論を効かせたい場合は、メソッドチェーンで書くのがベストプラクティスです(公式ドキュメント参照)。
const users = new Hono()
.get('/', (c) => c.json({ users: [] }))
.get('/:id', (c) => c.json({ id: c.req.param('id') }))
8. エラーハンドリングを一箇所にまとめる
app.onError() と HTTPException で例外処理を統一しましょう。
import { HTTPException } from 'hono/http-exception'
app.get('/protected', (c) => {
const token = c.req.header('Authorization')
if (!token) {
throw new HTTPception(401, { message: 'Unauthorized' })
}
return c.json({ ok: true })
})
app.onError((err, c) => {
console.error(err)
if (err instanceof HTTPException) {
return err.getResponse()
}
return c.json({ error: 'Internal Server Error' }, 500)
})
app.notFound((c) => c.json({ error: 'Not Found' }, 404))
9. JSXでサーバーサイドレンダリングも可能
HonoはJSXにも対応しています。軽量なランディングページやメールテンプレートに便利です。
/** @jsxImportSource hono/jsx */
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => {
return c.html(
<html>
<body>
<h1>Hello Hono!</h1>
</body>
</html>
)
})
hono/jsx/streaming を使えばストリーミングSSRも可能です。
10. テストがめちゃくちゃ書きやすい
Honoのアプリは app.request() で直接呼び出せるので、テストが簡単です。HTTPサーバーを起動する必要がありません。
import { describe, it, expect } from 'vitest'
import app from './index'
describe('GET /users/:id', () => {
it('should return user', async () => {
const res = await app.request('/users/1')
expect(res.status).toBe(200)
const json = await res.json()
expect(json.id).toBe('1')
})
})
Vitestとの相性も抜群で、CIも高速に回せます。
まとめ
Honoは軽量ながら、実務で必要な機能が一通り揃っている非常に完成度の高いフレームワークです。特に:
- エッジ環境(Cloudflare Workers) との相性が最高
- TypeScriptの型推論 が強力(RPCモードは革命的)
- Web標準ベース なのでどこでも動く
- バンドルサイズが小さい のでコールドスタートが速い
「ExpressからHonoに移行したら開発が楽しくなった」という声も多く、これから始めるバックエンド開発では第一候補になると思います。
📢 【期間限定・半額】Honoを本格的に学びたい方へ
もし「Honoをもっと体系的に学びたい」「実際にAPIとDBを繋げてRESTfulなアプリを作りたい」と思った方は、私がUdemyで公開している講座もぜひチェックしてみてください。
TypeScript × Hono × REST API × DB を ハンズオン形式 で学べる内容になっています。
👉 【半額クーポン付き】Hono + TypeScript でREST API & DB を構築する講座
⚠️ クーポンには期限があります。お早めにご利用ください!
最後まで読んでいただきありがとうございました!記事が役に立ったら、いいね・ストックしていただけると励みになります 🙌