11 人が閲覧(直近 30 日) Webシステム

Hono vs FastAPI(Python)の違い|Honoとは・メリット・使い方を実行結果で比較【Hono 4.13】

Hono vs FastAPI(Python)の違い|Honoとは・メリット・使い方を実行結果で比較【Hono 4.13】

Hono(ほの)は、Web標準のRequest/Response APIの上に作られたTypeScript製のWebフレームワークです。名前は日本語の「炎」に由来し、同じコードがCloudflare Workers、Deno、Bun、Node.js、AWS Lambdaなどで動きます。2026年9月29日時点のnpm最新版は4.13.11です。

「FastAPIとHonoのどちらを使うか」は、実のところ「PythonとTypeScriptのどちらでAPIを書くか」とほぼ同じ問いになります。この記事では、同じユーザー登録APIを両方で書いて実行した結果(検証エラーの応答、OpenAPIの出力、型の共有)で違いを比べ、そのうえでHonoの始め方、JWT認証、テスト、デプロイ先ごとの入口を説明します。

まとめ:Honoの要点とFastAPIとの選び分け

  • Honoとは:Web標準APIだけで書かれた、依存パッケージ0のTypeScript製Webフレームワーク。Pythonでは使えません。
  • FastAPIとの最大の違い:言語と実行環境です。FastAPIはPython+ASGIサーバー、Honoはエッジを含むJavaScriptランタイムで動きます。
  • 検証とドキュメント: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しか書かないチーム。
  • JWTの注意:Hono 4.11.4(2026年1月)以降、jwtミドルウェアはalgの指定が必須です。古い記事のコードは起動時に例外になります。

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認証、ロガー、ETagなどのミドルウェアは本体に同梱されており、別パッケージを探す必要はありません。

HonoとFastAPIの違いを比較表で整理

同じ「名前と年齢を受け取ってユーザーを作るAPI」を両方で書き、2026年9月29日に手元で実行しました。Hono側は hono 4.13.11 と @hono/zod-openapi 1.6.3(Node.js v26.5.0)、FastAPI側は fastapi 0.141.1 と pydantic 2.13.5(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からコード生成
最新版(2026-09-29) 4.13.11 0.141.1(2026-07-29)

検証エラーの応答: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",...}}です。

既存のFastAPIをHonoへ移すと、フロントエンドが「422なら入力エラー」と判定しているコードが素通りになります。移行時は、Hono側でdefaultHookを設定して422と独自形式を返すか、クライアントの判定を400に合わせるかを先に決めてください。

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を生成する方法で詳しく比べています。

速度比較の前提:402,820 ops/secはルーター同士の数値

Hono公式の「最速」は、Cloudflare Workers向けルーター同士の比較(Honoが402,820 ops/sec)です。FastAPIとの比較ではありません。実際のAPIの応答時間は、DBや外部APIの待ち時間と、Uvicornのワーカー数やエッジの配置で大きく変わります。速度だけを理由に言語を乗り換えるのはおすすめしません。

Hono Pythonの有無とPython Workers×FastAPIの構成

HonoはJavaScriptランタイム専用で、Python版はありません。「hono python」で探している場合、Pythonで同じ役割を担うのはFastAPIです。FastAPIの基本と起動方法はFastAPIとは|最短で動かす手順とasync/defの使い分けにまとめています。

「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でローカル起動します。Cloudflare Workers自体の料金や無料枠はCloudflare Workersとは?対応言語・無料枠・使い方・料金を参照してください。

Honoのメリットと採用を見送るべき条件

Honoを選ぶ4つの理由

  • 配置先を後から変えられる:Node.jsで作ったAPIを、アダプターと入口ファイルの差し替えだけでWorkersやLambdaへ移せます。
  • フロントエンドと型を共有できる:RPC機能で、サーバーのルート定義からクライアントの型が生成されます(後述)。
  • 依存が少ない:本体の依存パッケージは0。サプライチェーンの監査対象が小さく済みます。
  • ミドルウェアが同梱:CORS、JWT、JWK、Basic認証、CSRF、Secure Headers、Timeoutなどがhono/corsのようなパスで読み込めます。

FastAPIのままにすべき条件

次のどれかに当てはまるなら、Honoへ移す利点より損失の方が大きくなります。

  • 推論やデータ処理でPyTorch、pandas、scikit-learnを同じプロセスから呼んでいる。Honoにすると、別のPythonサービスを立ててHTTP越しに呼ぶ構成になります。
  • チームにTypeScriptを書く人がいない。
  • OpenAPIドキュメントを追加の作業なしで出したい。Honoではルート定義の書き直しが要ります。
  • ORMや管理画面まで一式ほしい。Hono本体にもFastAPIにも無いので、その場合はDjangoなど別の選択肢をWebアプリケーションフレームワークとは?主要フレームワーク比較と選び方で比べてください。

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以上」と書いていますが、@hono/node-server 2.1.3のpackage.jsonは"node": ">=20"を要求します。Node.js 18の環境ではアダプターのインストール時に警告が出るので、20以上に上げてから始めてください。

zod-openapiによる検証・型・OpenAPIの一元定義

比較表の結果を出したHono側のコードです。リクエストの形をZodで1回書けば、検証、ハンドラ内の型、OpenAPIの出力が揃います。

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: {
      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モデルと関数の型ヒントだけで済みます。FastAPIのSwagger UIの表示や本番での無効化はFastAPIのSwagger UI(/docs)自動生成で扱っています。

from fastapi import FastAPI
from pydantic import BaseModel, Field

app = FastAPI()

class UserIn(BaseModel):
    name: str = Field(min_length=1)
    age: int

@app.post("/users", status_code=201)
def create_user(user: UserIn):
    return {"id": 1, "name": user.name}

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にルートの型が残らず、tscでは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で、JWTの署名アルゴリズムの取り違え(algorithm confusion、GHSA-f67f-6cw9-8mq4)を防ぐため、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.11でalgを省くと、jwt()を呼んだ時点で「JWT auth middleware requires options for “alg”」という例外が出ました。2025年以前の解説記事のコードをそのまま貼ると起動しないのはこのためです。トークンなしでアクセスすると、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種に増えた時点で使えるようになりました。「HonoはRSAの検証が苦手」という情報は古いものです。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と同じくメソッドチェーンで書いたルートにだけ型が付き、client.search.$get({ query: { q: 'hono' } })のように呼べます。FastAPIのTestClientと同じ位置づけですが、FastAPI側はfastapi単体で入れた場合、テスト用HTTPクライアント(httpx)を別途インストールする必要があります(fastapi[standard]には同梱)。なおStarlette 1.7.0以降は、httpxのままだとhttpx2への移行を促す非推奨の警告が出ます。

デプロイ先別の入口: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() 本体のhono/aws-lambdaはv5で削除予定

2026年9月28日の4.13.10で、ランタイム別のアダプターは本体から別パッケージ(@hono/aws-lambda、@hono/bun、@hono/deno、@hono/cloudflare-workers、@hono/lambda-edge、@hono/netlify、@hono/vercel、@hono/service-worker)へ分離されました。本体のhono/aws-lambdaなどは4.13.11でも読み込めますが非推奨で、v5で削除されます。AWS Lambdaでは@hono/aws-lambda(1.0.0)のhandle()を使ってください。また4.13.11では、serveStaticがパスを二重にデコードし、静的パスに掛けたミドルウェアを迂回できる脆弱性(GHSA-5r4p-p66f-jhc7)が修正されました。Bun・Deno・Cloudflare Workersで静的ファイルを配信するなら、この版以上を使ってください。

Azure Functionsは、公式ドキュメントも「最初からAzure Functions向けに作ったわけではない」と断ったうえで、サードパーティのアダプター(@marplex/hono-azurefunc-adapter。最終リリースは2024年10月の1.0.1)を案内しています。app.http()のrouteに{*proxy}を指定して全パスをHonoへ渡し、ハンドラにazureHonoHandler(honoApp.fetch)を渡します。

Azure Functionsでは、既定でURLの先頭に/apiが付きます。host.jsonのextensions.http.routePrefixを空文字にするか、Hono側のルートをすべて/api始まりにするかのどちらかが必要です。ここを忘れると、Node.jsで動かしていたときと同じURL(例:/users)へのリクエストが、func startでのローカル実行でもAzure上でも404になります。Honoの上にファイルベースのルーティングを載せたい場合はHonoXとは?Hono×Viteのメタフレームワークも検討してください。

よくある質問

HonoとFastAPIはどちらを選ぶべきですか?

書く言語で決めるのが基本です。Pythonのライブラリを同じプロセスで使うAPIならFastAPI、エッジで動かしたい場合やTypeScriptのフロントエンドと型を共有したい場合はHonoを選んでください。

HonoはPythonで使えますか?

使えません。HonoはJavaScriptランタイム専用です。Pythonでエッジに配置したい場合は、Cloudflare Python WorkersでFastAPIを動かす方法があります。

Honoのメリットは何ですか?

依存パッケージが0で軽く、同じコードをCloudflare Workers、Deno、Bun、Node.jsなどで動かせることです。RPCでクライアントとサーバーの型を共有できる点も、TypeScriptで開発するチームには大きな利点です。

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と違い標準では付いていないため、パッケージの追加とルート定義の書き直しが必要です。

関連記事

お気に入りに入れた記事の一覧

この記事は以下の記事からリンクされています

資料請求

今日のトレンド記事 直近 24 時間で、いつもより多く読まれている記事

  1. 2026.10.23 テックブログ Renta!不正アクセスでサービス停止:再開未定の現状と運営側の停止判断・証拠保全・段階的再開
  2. 2025.01.09 コラム アドレサブルCM(Addressable CM)とは?フジテレビで出る表示の意味・仕組みと視聴データの扱い
  3. 2026.07.09 テックブログ Muse Imageとは?Metaの画像生成AIの使い方・API料金(1枚0.01ドル)と日本での提供状況【2026年10月】
  4. 2025.12.10 コラム 今の50代は何世代?バブル世代・団塊ジュニア・氷河期の境目と特徴
  5. 2026.04.13 コラム ゴルフ賞金の税金はいくら?1,000万円で源泉194万円・賞金額別の手取りと副賞の車、海外賞金【令和8年分】

RELATED POSTS 関連記事

目次