ミドルウェア
ハンドラの前後で実行される関数。認証・ロギング・キャッシュ・CORS・圧縮等を再利用可能な形で挟める。
(c, next) => ... という Express 風のシグネチャ。
基本
app.use(async (c, next) => {
console.log(`-> ${c.req.method} ${c.req.path}`)
await next() // 後続を実行
console.log(`<- ${c.res.status}`) // 応答後の処理
})
next() の前: 受信時の処理 / 後: 応答時の処理 / 呼ばない: 短絡(ハンドラに行かない)
適用範囲
// 全ルート
app.use(middleware)
// 特定 path
app.use("/api/*", middleware)
// 複数を順に
app.use("/api/*", logger(), cors(), authMiddleware)
// 特定ルート単体
app.get("/admin", auth, (c) => ...)
組み込みミドルウェア(hono/...)
| 名前 | 用途 |
|---|---|
logger | リクエストログ |
cors | CORS ヘッダー |
csrf | CSRF 検証(Origin ベース) |
secure-headers | セキュリティヘッダー(X-Frame-Options 等) |
basic-auth | Basic 認証 |
bearer-auth | Bearer Token 認証 |
jwt | JWT 検証 |
cache | レスポンスキャッシュ |
etag | ETag 自動付与 |
compress | gzip / deflate |
pretty-json | JSON 整形(?pretty) |
timing | Server-Timing ヘッダー |
request-id | X-Request-Id 付与 |
method-override | POST から PUT/DELETE を擬似 |
body-limit | ボディサイズ制限 |
trailing-slash | 末尾スラッシュ統一 |
language | Accept-Language 解析 |
ip-restriction | IP 制限 |
logger
import { logger } from "hono/logger"
app.use(logger())
cors
import { cors } from "hono/cors"
app.use("/api/*", cors({
origin: ["https://example.com", "http://localhost:5173"],
allowHeaders: ["Authorization", "Content-Type"],
allowMethods: ["GET", "POST", "PUT", "DELETE"],
exposeHeaders: ["Content-Length"],
maxAge: 600,
credentials: true,
}))
secure-headers
import { secureHeaders } from "hono/secure-headers"
app.use(secureHeaders({
xFrameOptions: "DENY",
contentSecurityPolicy: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'nonce-...'"],
},
strictTransportSecurity: "max-age=31536000; includeSubDomains",
}))
cache(Cloudflare Cache API 連携)
import { cache } from "hono/cache"
app.get("/api/heavy", cache({
cacheName: "api-v1",
cacheControl: "max-age=3600",
}), (c) => {
return c.json({ data: ... })
})
compress
import { compress } from "hono/compress"
app.use(compress())
etag
import { etag } from "hono/etag"
app.use(etag())
自作ミドルウェア
シンプルなロガー
app.use(async (c, next) => {
const start = Date.now()
await next()
const ms = Date.now() - start
console.log(`${c.req.method} ${c.req.path} ${c.res.status} ${ms}ms`)
})
認証チェック
type Variables = { user: User }
const app = new Hono<{ Variables: Variables }>()
const auth = async (c: any, next: any) => {
const token = c.req.header("authorization")?.replace("Bearer ", "")
if (!token) return c.text("Unauthorized", 401)
try {
const user = await verifyToken(token)
c.set("user", user)
await next()
} catch {
return c.text("Unauthorized", 401)
}
}
app.use("/api/*", auth)
app.get("/api/me", (c) => c.json(c.get("user")))
関数ファクトリパターン
import type { MiddlewareHandler } from "hono"
function rateLimiter(limit: number): MiddlewareHandler {
const counts = new Map<string, { count: number, reset: number }>()
return async (c, next) => {
const ip = c.req.header("cf-connecting-ip") ?? "anon"
const now = Date.now()
const record = counts.get(ip) ?? { count: 0, reset: now + 60_000 }
if (now > record.reset) {
record.count = 0
record.reset = now + 60_000
}
record.count++
counts.set(ip, record)
if (record.count > limit) {
return c.text("Too Many Requests", 429)
}
await next()
}
}
app.use("/api/*", rateLimiter(60))
レスポンス書き換え
app.use(async (c, next) => {
await next()
// 全レスポンスにヘッダーを追加
c.header("X-Powered-By", "Hono")
})
順序が重要
app.use の登録順に実行される。ロガー → CORS → 認証 → ハンドラのように、
外側から内側へ並べる。
app.use(logger()) // 1. リクエストをログ
app.use(cors()) // 2. CORS
app.use("/api/*", auth) // 3. 認証
app.get("/api/data", handler) // 4. ハンドラ
条件付き分岐
app.use(async (c, next) => {
if (c.req.method === "OPTIONS") {
return c.text("", 204) // preflight は短絡
}
await next()
})
onError との連携
ミドルウェア内の例外は app.onError で受けられる。
import { HTTPException } from "hono/http-exception"
app.use("/api/*", async (c, next) => {
if (!c.req.header("x-api-key")) {
throw new HTTPException(401, { message: "API Key required" })
}
await next()
})
app.onError((err, c) => {
if (err instanceof HTTPException) {
return err.getResponse()
}
console.error(err)
return c.json({ error: "Internal Server Error" }, 500)
})
テスト
import { Hono } from "hono"
import { auth } from "./middleware"
const app = new Hono().use(auth).get("/me", (c) => c.text("ok"))
const res = await app.request("/me", { headers: { Authorization: "Bearer valid" } })
expect(res.status).toBe(200)
設計のコツ
✓ ミドルウェアは1責任に保つ
✓ 関数ファクトリで設定値を受けるパターンを使う
✓ 認証等の重要な middleware はテストを書く
✓ ログ・メトリクスは executionCtx.waitUntil でレスポンスをブロックしない(CF Workers)