Hono(ほの)は、Web標準のRequest/Response APIの上に作られたTypeScript製のWebフレームワークです。名前は日本語の「炎」に由来し、同じコードがCloudflare Workers、Deno、Bun、Node.js、AWS Lambdaなどで動きます。2026年11月1日時点のnpm最新版は4.13.16で、次のメジャー版5.0.0のリリース候補(RC)も公開されています。
「FastAPIとHonoのどちらを使うか」は、ほぼ「PythonとTypeScriptのどちらでAPIを書くか」と同じ問いです。この記事では、同じユーザー登録APIを両方で書き、同じマシンで処理性能を測り、検証エラーの応答・OpenAPIの出力・型の共有を実行結果で比べます。そのうえでHonoの始め方、JWT認証、テスト、デプロイ先ごとの入口、v5で変わる点を説明します。
まとめ:Honoの要点とFastAPIとの選び分け
- Honoとは:Web標準APIだけで書かれた、依存パッケージ0のTypeScript製Webフレームワーク。Pythonでは使えません。
- 最大の違い:言語と実行環境です。FastAPIはPython+ASGIサーバー、Honoはエッジを含むJavaScriptランタイムで動きます。
- 速度:同じマシンの1プロセス同士で、HonoはFastAPI(async def)の約4.5倍のリクエストを処理しました。FastAPIもワーカーを4つに増やすと差は約1.4倍まで縮みます。
- 検証とドキュメント:FastAPIは検証失敗で422を返し、OpenAPI 3.1.0と/docsを標準で出します。Honoは@hono/zod-openapiを足すと400を返し、OpenAPI 3.0/3.1を出力します。
- Honoを選ぶ場面:Cloudflare Workersなどエッジで動かしたい、フロントエンドとTypeScriptの型を共有したいとき。
- FastAPIを選ぶ場面:PyTorchやpandasなどPythonのライブラリを同じプロセスで呼ぶAPI、Pythonしか書かないチーム。
- 版の注意:v4.11.4以降のjwtミドルウェアは
algの指定が必須です。v5(RC)はESM専用でNode.js 22.12以上が要ります。
Honoの定義と動作するランタイム
Honoはnpmで2021年12月に0.0.1が公開され、2024年2月9日にv4.0.0が出ました。作者はYusuke Wada氏です。公式ドキュメントが挙げる動作先は、Cloudflare Workers、Fastly Compute、Deno、Bun、Vercel、Netlify、AWS Lambda、Lambda@Edge、Node.jsです。
HonoはNode.js固有のAPIに頼らず、どのランタイムにもあるWeb標準のRequestとResponseだけを扱うので、多くのランタイムで同じコードが動きます。npmのpackage.jsonに依存パッケージは1つもなく、公式によるとhono/tinyプリセットはminify後14KB未満に収まります。書き味はExpressに近く、app.get('/', (c) => c.text('Hello'))のようにルートとハンドラを並べます。CORS、JWT、Basic認証、CSRF、ロガー、ETagなどのミドルウェアは本体に同梱されています。
HonoとFastAPIの違いを比較表で整理
同じ「名前と年齢を受け取ってユーザーを作るAPI」を両方で書き、2026年11月1日に手元で実行しました。Hono側は hono 4.13.16 と @hono/zod-openapi 1.6.3(Node.js v26.5.0)、FastAPI側は fastapi 0.162.0 と pydantic 2.14.2(Python 3.12)です。
| 比較軸 | Hono | FastAPI |
|---|---|---|
| 言語 | TypeScript/JavaScript | Python 3.10以上 |
| 実行環境 | Workers・Deno・Bun・Node.jsなど | ASGIサーバー(Uvicorn等) |
| 入力検証 | Zodなど(ミドルウェアで追加) | Pydantic(標準) |
| 検証失敗時の応答 | 400(zod-openapi既定) | 422 |
| OpenAPI出力 | 3.0/3.1(追加パッケージ) | 3.1.0(標準) |
| ドキュメント画面 | @hono/swagger-uiを追加 | /docs・/redocが標準 |
| クライアントとの型共有 | RPC(hono/client) | OpenAPIからコード生成 |
| 1プロセスの処理数(GET) | 約10,600 req/s | 約2,340 req/s(async def) |
| GitHubスター | 32,764 | 104,473 |
| 週間ダウンロード | 約1.01億件(npm) | 約1.22億件(PyPI) |
| 最新版 | 4.13.16(2026-11-01) | 0.162.0(2026-10-23) |
スター数は2026年11月1日にGitHub APIで取得した値です。ダウンロード数は同じ期間(2026年10月23日〜29日)で揃え、npmはnpmのダウンロードAPI、PyPIはPyPIの公開データを集計するpypistatsのミラー除外値です。どちらもCIの再取得を含みます。
検証エラーの応答:Honoは400、FastAPIは422
空の名前と文字列の年齢({"name": "", "age": "x"})を送ると、FastAPIは422を返し、detail配列にstring_too_shortとint_parsingの2件を入れます。@hono/zod-openapiで書いたHonoは400を返し、本文は{"success":false,"error":{"name":"ZodError",...}}です。@hono/zod-validatorのソースが検証失敗時にc.json(result, 400)を返す作りになっています。
既存のFastAPIをHonoへ移すと、フロントエンドが「422なら入力エラー」と判定しているコードが素通りになります。クライアントを直せないなら、OpenAPIHonoのdefaultHookでFastAPIに近い形の422を返します。
const app = new OpenAPIHono({
defaultHook: (result, c) => {
if (!result.success) {
return c.json(
{ detail: result.error.issues.map((i) => ({ loc: ['body', ...i.path], msg: i.message })) },
422
)
}
},
})
このフックを付けて同じ不正な入力を送ると、422と{"detail":[{"loc":["body","name"],"msg":"Too small: expected string to have >=1 characters"},{"loc":["body","age"],...}]}が返りました。FastAPIはlocの先頭に入力の場所(body)を入れるので、ここを合わせないと入力欄ごとのエラー表示が壊れます。一方、msgの文言はZodのもので、FastAPIのtype(string_too_shortなど)は付きません。エラーの種類で分岐しているクライアントは、この対応表も作る必要があります。
APIドキュメント自動生成の手間
FastAPIは何も足さずに/openapi.json(openapi 3.1.0)と、Swagger UIの/docs、ReDocの/redocを配信します。型ヒントとPydanticモデルを書いた時点でドキュメントも揃う点は、FastAPIが明確に勝っています。
Honoの本体にはOpenAPI生成の機能がありません。@hono/zod-openapiでルートをスキーマ付きで定義し、app.doc()で3.0、app.doc31()で3.1を出力し、画面は@hono/swagger-uiで足します。ルートをcreateRoute()で書き直す手間がかかる代わりに、検証・型・ドキュメントの定義が1か所にまとまります。ZodスキーマからOpenAPIを作る方法の選び分けはZodからOpenAPIを生成する方法で比べています。
処理性能の実測:同じマシンでHonoは1プロセス約1.1万req/s
競合記事や公式サイトの「Honoは速い」には、FastAPIと同じ条件で比べた数字がありません。公式ベンチマークの402,820 ops/secは、Cloudflare Workers向けルーター同士の比較(M1 Pro)です。そこで、上の2つのAPIを同じマシンで起動し、負荷ツールautocannon 8.0.0で50接続×10秒を流しました。1ワーカーの値は2回の平均、4ワーカーは2回の範囲です。
- マシン:Intel Core i9-9880H(8コア16スレッド)、メモリ16GB、macOS 26.6。autocannonも同じマシンで動かし、ほかの処理も動いていたため、ロードアベレージは約4〜7でした。
- Hono:@hono/node-server 2.1.5の
serve()、1プロセス。 - FastAPI:
uvicorn[standard]0.54.0(uvloopとhttptoolsを含む)。defとasync defの両方を1ワーカーで、async defは--workers 4でも測定。 - GETは固定のJSONを返すだけ、POSTは下の章のコード(入力検証あり・FastAPIは
response_modelで応答も検証)です。
| 構成 | GET(JSON返却) | POST(検証あり) | GETのp50遅延 |
|---|---|---|---|
| Hono(Node.js・1プロセス) | 約10,600 req/s | 約5,500 req/s | 2ms |
| FastAPI async def(1ワーカー) | 約2,340 req/s | 約1,730 req/s | 15ms |
| FastAPI def(1ワーカー) | 約1,350 req/s | 約860 req/s | 39ms |
| FastAPI async def(4ワーカー) | 6,851〜8,086 req/s | 4,069〜5,629 req/s | 4ms |
1プロセス同士なら、HonoはFastAPI(async def)の約4.5倍、検証付きPOSTでも約3.2倍を処理しました。
FastAPIは書き方と起動方法で大きく変わります。defのハンドラはスレッドプールで動くため、何も待たない処理ではasync defの6割弱しか出ませんでした。さらにuvicornを[standard]なしで入れた環境では、同じdefのGETが約790 req/sまで落ちました。async defとdefの使い分けはFastAPIとは?最短の起動手順とasync/defの使い分け、ワーカー数の決め方はUvicornとは?Gunicornとの違いと起動・本番設定で扱っています。
FastAPIもワーカーを4つにするとGETで平均約7,470 req/sまで伸び、Honoの1プロセスとの差は約1.4倍に縮みます。ただし2回の値の振れが大きく、メモリもワーカーの数だけ増えます。
ただし、この差はDBや外部APIを待たない処理での差です。1リクエストでDBに20ms待つAPIなら、応答時間の大半は待ち時間になり、フレームワークの差は埋もれます。速度を理由に乗り換えてよいのは、サーバー代やエッジのCPU時間の上限が効いてくる場合です。
Hono Pythonの有無とPython Workers×FastAPIの構成
HonoはJavaScriptランタイム専用で、Python版はありません。「hono python」で探している場合、Pythonで同じ役割を担うのはFastAPIです。
「Honoのようにエッジで動かしたいが、書くのはPython」という要件なら、Cloudflare WorkersのPython Workersが使えます。Cloudflareの公式手順はFastAPIを対応パッケージとして挙げ、Workers側が用意するASGIサーバーにアプリを渡す書き方を示しています。
# src/main.py
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"Hello": "World"}
from workers import asgi
Default = asgi.entrypoint(app)
wrangler.jsoncに"compatibility_flags": ["python_workers"]を書き、pyproject.tomlはrequires-python = ">=3.13"とし、uv run pywrangler devでローカル起動します。料金はHonoのWorkerと同じで、公式の料金表ではFreeが1日10万リクエスト・1回のCPU時間10ミリ秒まで、Paidは月5ドルで1,000万リクエストと3,000万CPUミリ秒を含み、超過は100万リクエストごとに0.30ドルです。含まれる3,000万CPUミリ秒を超えると100万CPUミリ秒ごとに0.02ドルが加わるので、リクエストが多いAPIほど1リクエストあたりのCPU消費が料金に効きます。Workers自体の使い方はCloudflare Workersとは?料金・無料枠・Workers Paidの中身と対応言語・使い方を参照してください。
Honoのメリットと採用を見送るべき条件
Honoを選ぶ理由
- 配置先を後から変えられる:Hono本体はWeb標準APIだけで動くので、ルートとミドルウェアの部分はそのままWorkersやLambdaへ持っていけます。ただしアプリが
fsなどNode.js固有のAPIや、それに依存するパッケージを使っている場合は、WorkersのNode.js互換の対応範囲を確かめる必要があります。 - フロントエンドと型を共有できる:RPC機能で、サーバーのルート定義からクライアントの型が得られます(後述)。
- 依存が少ない:本体の依存パッケージは0。サプライチェーンの監査対象が小さく済みます。
- ミドルウェアが同梱:CORS、JWT、JWK、Basic認証、CSRF、Secure Headers、Timeoutなどを
hono/corsのようなパスで読み込めます。
Honoへ移さない方がよい条件
次のどれかに当てはまるなら、Honoへ移す利点より損失の方が大きくなります。
- 推論やデータ処理でPyTorch、pandas、scikit-learnを同じプロセスから呼んでいる。Honoにすると、別のPythonサービスを立ててHTTP越しに呼ぶ構成になります。
- チームにTypeScriptを書く人がいない。
- OpenAPIドキュメントを追加の作業なしで出したい。Honoではルート定義の書き直しが要ります。
- ORMや管理画面まで一式ほしい。Hono本体にもFastAPIにも無いので、その場合はDjangoなどをWebアプリケーションフレームワークとは?主要フレームワークの比較と選び方で比べてください。
既存システムとの連携APIをどちらで作るか、Pythonの推論サービスとTypeScriptのフロントをどうつなぐかといった構成の判断は、API開発・システム連携でも相談を受けています。
Honoの始め方:create-honoから最初のAPIまで
雛形はnpm create hono@latest my-appで作り、表示されるテンプレートから配置先(nodejs、cloudflare-workers、bunなど)を選びます。Node.jsで動かす最小構成は次のとおりです。
import { serve } from '@hono/node-server'
import { Hono } from 'hono'
import { cors } from 'hono/cors'
const app = new Hono()
app.use('/api/*', cors({ origin: 'http://localhost:5173' }))
app.get('/api/hello', (c) => c.json({ message: 'Hello Hono!' }))
serve({ fetch: app.fetch, port: 3000 })
Originヘッダー付きで/api/helloを叩くと、200とAccess-Control-Allow-Origin: http://localhost:5173が返ります。
Node.jsの版には注意が要ります。公式のNode.jsページは「18.x系なら18.14.1以上」と書いていますが、@hono/node-server 2.1.5のpackage.jsonは"node": ">=20"を要求します。さらにv5はNode.js 22.12以上が必要なので、これから始めるなら22系以上で環境を作るのが無駄がありません。
zod-openapiによる検証・型・OpenAPIの一元定義
比較表の結果を出したHono側のコードです。リクエストの形をZodで1回書けば、検証、ハンドラ内の型、OpenAPIの出力が揃います。required: trueを書かないと本文が任意扱いになり、本文なしのPOSTが201で通りました(FastAPIは422)。
import { OpenAPIHono, createRoute, z } from '@hono/zod-openapi'
import { swaggerUI } from '@hono/swagger-ui'
const app = new OpenAPIHono()
const route = createRoute({
method: 'post',
path: '/users',
request: {
body: {
required: true,
content: {
'application/json': {
schema: z.object({ name: z.string().min(1), age: z.number().int() }),
},
},
},
},
responses: {
201: {
content: { 'application/json': { schema: z.object({ id: z.number(), name: z.string() }) } },
description: 'created',
},
},
})
app.openapi(route, (c) => {
const { name } = c.req.valid('json') // name は string 型
return c.json({ id: 1, name }, 201)
})
app.doc('/doc', { openapi: '3.0.0', info: { title: 'API', version: '1.0.0' } })
app.get('/ui', swaggerUI({ url: '/doc' }))
FastAPIで同じことを書くと、Pydanticモデルと関数の型ヒントだけで済みます。Swagger UIの表示や本番での無効化はFastAPIのSwagger UI(/docs)の使い方で扱っています。Zod以外のValibotやArkTypeで検証したい場合は、共通の型を受け取る@hono/standard-validatorも選べます(仕組みはStandard Schemaとは?を参照)。
from fastapi import FastAPI
from pydantic import BaseModel, Field
app = FastAPI()
class UserIn(BaseModel):
name: str = Field(min_length=1)
age: int
class UserOut(BaseModel):
id: int
name: str
@app.post("/users", status_code=201, response_model=UserOut)
def create_user(user: UserIn):
return {"id": 1, "name": user.name}
同じ仕様に見えても型の扱いは違います。ageに文字列の"30"を送ると、Pydanticは既定で数値へ変換して201を返し、Zodのz.number()は400(フック付きなら422)で拒否しました。移行するときは、文字列の数値を受け入れるかを先に決め、Hono側でz.coerce.number()にするか、FastAPI側をField(strict=True)にして揃えます。
RPCでクライアントと型を共有する手順と注意点
HonoのRPCは、サーバーのアプリの型(typeof app)をクライアントに渡し、hc関数でエンドポイントを型付きで呼ぶ仕組みです。FastAPIでフロントエンドの型を得るにはOpenAPIからのコード生成を挟みますが、Honoは同じリポジトリ内なら生成の工程が要りません。
// server.ts
import { Hono } from 'hono'
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'
const app = new Hono().post(
'/todos',
zValidator('json', z.object({ title: z.string() })),
(c) => c.json({ id: 1, title: c.req.valid('json').title }, 201)
)
export type AppType = typeof app
// client.ts
import { hc } from 'hono/client'
import type { AppType } from './server'
const client = hc<AppType>('http://localhost:3000')
const res = await client.todos.$post({ json: { title: 'buy milk' } })
if (res.status === 201) {
const data = await res.json() // { id: number; title: string }
console.log(data.id, data.title)
}
TypeScript 7.0.2のtscで確かめると、titleをtitelと打ち間違えた呼び出しは「TS2561: Object literal may only specify known properties」でコンパイルが止まりました。サーバー側の項目名を変えたとき、クライアントの呼び出し箇所がビルドで見つかります。
つまずきやすい点が2つあります。1つは、typeof appを渡すならルートをnew Hono().post(...)のようにメソッドチェーンで書く必要があることです。app.post(...)を別の文に分けるとtypeof appにルートの型が残らず、clientがunknownになります(TS18046)。別の文で書くなら、const route = app.post(...)と戻り値を受けてtypeof routeを渡します。もう1つは、zValidatorを挟んだルートではres.json()の型が「成功時の本文」と「検証エラー」の合併型になることです。上の例のようにres.statusかres.okで分岐しないと、data.idの参照が「TS2339: Property ‘id’ does not exist」になります。
JWT認証の実装とalg必須化(v4.11.4)
JWTの検証は同梱のhono/jwtミドルウェアで書けます。2026年1月13日のv4.11.4で、署名アルゴリズムの取り違え(algorithm confusion)を防ぐため、jwtとjwkの両ミドルウェアでalgが必須オプションになりました。
import { Hono } from 'hono'
import { jwt } from 'hono/jwt'
import type { JwtVariables } from 'hono/jwt'
const app = new Hono<{ Variables: JwtVariables }>()
app.use('/auth/*', jwt({ secret: process.env.JWT_SECRET!, alg: 'HS256' }))
app.get('/auth/me', (c) => c.json(c.get('jwtPayload')))
4.13.16でalgを省くと、jwt()を呼んだ時点で「JWT auth middleware requires options for “alg”」という例外が出ました。トークンなしでアクセスすると、401とWWW-Authenticate: Bearer ... error="invalid_request"が返ります。Cloudflare Workersではprocess.envではなく、ミドルウェア内でjwt({ secret: c.env.JWT_SECRET, alg: 'HS256' })を組み立てて呼ぶのが公式の書き方です。
RS256などの公開鍵方式は、2024年4月のv4.2.0で対応アルゴリズムが3種から13種に増えた時点で使えるようになりました。Auth0やCognitoのようにJWKSのURLで公開鍵を配る認証基盤なら、hono/jwkのjwk({ jwks_uri, alg: ['RS256'] })を使います。JWKミドルウェアはHS256などの共通鍵方式を拒否し、トークンのヘッダーにkidが無いと検証しません。
テストの書き方:app.requestとtestClient
Honoのアプリはサーバーを起動せずにテストできます。app.request('/users', { method: 'POST', body: ... })がResponseを返すので、VitestやJestからそのまま検証できます。型付きで呼びたい場合はhono/testingのtestClient(app)を使い、RPCと同じくメソッドチェーンで書いたルートにだけ型が付きます。
FastAPIのTestClientが同じ位置づけです。fastapi単体で入れた場合はテスト用HTTPクライアントを別途入れる必要があり、Starlette 1.10.0でhttpxを使うと「Using httpx with starlette.testclient is deprecated; install httpx2 instead」という警告が出ました。httpx2(2.16.0)を追加すると、httpxが残っていても警告は消えました。
デプロイ先別の入口:Cloudflare Workers・Azure Functions・Node.js
Honoのアプリ本体は共通で、配置先ごとに変わるのは入口ファイルとアダプターです。
| 配置先 | 入口 | 前提 |
|---|---|---|
| Cloudflare Workers | export default app |
Wrangler |
| Node.js | @hono/node-serverのserve() |
2.x系はNode.js 20以上 |
| Azure Functions | @marplex/hono-azurefunc-adapter | Functions V4・Node.js 22/24 |
| AWS Lambda | @hono/aws-lambdaのhandle() |
4.13.10で本体から分離 |
Azure Functionsでは、公式ドキュメントがサードパーティのアダプター(@marplex/hono-azurefunc-adapter。最終リリースは2024年10月の1.0.1)を案内しています。app.http()のrouteに{*proxy}を指定して全パスをHonoへ渡し、ハンドラにazureHonoHandler(honoApp.fetch)を渡します。既定でURLの先頭に/apiが付くため、host.jsonのextensions.http.routePrefixを空文字にするか、Hono側のルートをすべて/api始まりにします。ここを忘れると、Node.jsで動かしていたときと同じURL(例:/users)へのリクエストが404になります。Honoの上にファイルベースのルーティングを載せたい場合はHonoXとは?Hono×Viteのメタフレームワークも検討してください。
Hono v5 RCで変わる点と4.13.16へ上げる理由
v5は2026年10月8日に5.0.0-rc.0、10日にrc.1が出ており、npmではhono@nextで入ります。2026年11月1日時点で、リリースノートと移行ガイドに正式版の公開日の記載はありません。移行ガイドの変更のうち、今から書くコードに効くのは次の4つです。
- ESM専用:CommonJSのビルドが無くなり、Node.jsは22.12以上が必要です(22.12以上なら
require('hono')も動きます)。 - c.req.json()がunknownを返す:rc.1を
tscにかけると、const b = await c.req.json(); b.nameは「TS18046: ‘b’ is of type ‘unknown’」で止まりました。c.req.json<{ name: string }>()で型を渡すか、バリデーターのc.req.valid()を使います。 - onErrorの引数が変わる:
app.onError((err, c) => ...)はapp.onError((c) => ...)になり、エラーはc.errorから読みます。旧形式のままではTS2339になりました。 - アダプターの削除:
hono/aws-lambdaなどは@hono/*パッケージへの書き換えが必須で、hono/cloudflare-pagesは代わりなしで削除されます。
v4を使い続ける場合も、4.13.16(2026年11月1日)までは上げてください。この版で、mount()経由でマウント先のミドルウェアを迂回できる問題、既定のルーターがワイルドカードのミドルウェアを適用しない場合がある問題、validator('form')が追加パラメーター付きのmultipartを検証しない問題など、5件のセキュリティ修正が入りました。認証をパス単位のミドルウェアで掛けているアプリは影響を受けます。
よくある質問
HonoとFastAPIはどちらを選ぶべきですか?
書く言語で決めるのが基本です。Pythonのライブラリを同じプロセスで使うAPIならFastAPI、エッジで動かしたい場合やTypeScriptのフロントエンドと型を共有したい場合はHonoを選んでください。
HonoはFastAPIより速いですか?
同じマシンの1プロセス同士では、Honoが約4.5倍のリクエストを処理しました。ただしFastAPIもワーカーを増やせば差は縮み、DBを待つ処理が中心のAPIではフレームワークの差はほとんど表に出ません。
HonoはPythonで使えますか?
使えません。HonoはJavaScriptランタイム専用です。Pythonでエッジに配置したい場合は、Cloudflare Python WorkersでFastAPIを動かす方法があります。
HonoでJWT認証は実装できますか?
できます。同梱のhono/jwtミドルウェアで検証でき、v4.11.4以降はalgの指定が必須です。RS256などの公開鍵方式にも対応し、JWKSのURLから鍵を取る場合はhono/jwkが受け持ちます。
HonoでもSwagger UIのようなAPIドキュメントを出せますか?
出せます。@hono/zod-openapiでOpenAPIの定義を生成し、@hono/swagger-uiで画面を配信します。FastAPIと違い標準では付いていないため、パッケージの追加とルート定義の書き直しが必要です。