FastAPIは、Pythonの型ヒントをそのままAPIの仕様として使うWebフレームワークです。関数の引数に item_id: int と書くだけで、型変換・入力検証・エラーレスポンス・OpenAPIドキュメントがまとめて手に入ります。公式チュートリアルの起動手順は uvicorn main:app --reload から fastapi dev に変わっており、2026年10月28日に「fastapi」の検索上位から取得した日本語の解説記事5本のうち、4本は uvicorn main:app による旧手順のままで、fastapi dev を使う記事はありませんでした。この記事は2026年10月23日公開の最新版0.162.0をPython 3.12の仮想環境で動かした結果をもとに、最短の起動手順、async def と def の使い分け、そして0.140以降に変わった挙動を整理します。
まとめ:FastAPIの要点(0.162.0時点)
- 正体:HTTPの処理はStarlette、データ検証はPydanticが担い、FastAPIは両者を型ヒントでつなぐ層です。ORMや管理画面は持ちません。ライセンスはMITで、商用利用もできます。
- 要件:最新版は0.162.0。Python 3.10以上が必須で、Python 3.9の環境ではエラーにならず、古い0.128.8が選ばれます。
- 起動:
pip install "fastapi[standard]"の後、開発はfastapi dev、本番はfastapi runを使います。dev は127.0.0.1で自動リロード付き、run は0.0.0.0でリロードなしです。 - 非同期:
async defの中でブロッキングI/Oや長時間の同期処理を直接実行すると、そのワーカーのイベントループが止まります。0.5秒の同期処理を10件同時に送ると、async defでは6.0秒、defでは0.7秒でした。迷ったらdefで書きます。 - 版の変化:0.151.0からJSONの
nullを既定値で置き換えなくなり、0.155.0ではQuery(custom=...)がTypeErrorになりました。2026年7月以前に書かれたコードは、更新前にこの2点を確認してください。 - 選び分け:管理画面・認証・ORMが最初から要るならDjangoを選びます。JSONを返すAPIサーバー単体ならFastAPIが向いています。
FastAPIとは:型ヒントをAPIの仕様にするPythonフレームワーク
FastAPIはSebastián Ramírez氏が開発するPython製のWebフレームワークです。公式ドキュメントの依存関係の節は「FastAPI depends on Pydantic and Starlette.」と書いており、HTTPの処理をStarletteに、値の検証と変換をPydanticに任せています。FastAPI自身が受け持つのは、関数の型ヒントを読み取って両者をつなぎ、OpenAPIスキーマを生成する部分です。土台のStarletteで見つかった脆弱性はFastAPIのアプリにも影響します。版の確かめ方はStarletteの脆弱性CVE-2026-48710とは|Hostヘッダで認可を迂回される条件と修正手順で扱っています。
「速い」と言われる根拠として公式が挙げるのは第三者ベンチマークのTechEmpowerで、Uvicorn上のFastAPIより上にいるのは内部で使っているStarletteとUvicornだけです。実際の応答時間は、アプリの処理内容やASGIサーバー、イベントループの実装に左右されます。Uvicornが使うuvloopはWindowsでは動かないため、Windowsでは標準のasyncioで動きます。
型ヒントから得られる入力検証とAPIドキュメント
型ヒントを付けた関数定義から、入力の型変換・検証とAPIドキュメントを生成できます。
| 得られるもの | 元になる記述 |
|---|---|
| リクエストの型変換 | 引数の型ヒント(item_id: int) |
| 入力検証と422応答 | Pydanticモデル・型ヒント |
OpenAPIスキーマ(/openapi.json) |
関数シグネチャ全体 |
対話型ドキュメント(/docs・/redoc) |
OpenAPIスキーマ |
典型的な用途は、REST APIやマイクロサービスのバックエンド、機械学習モデルの推論エンドポイント、SPAから呼ぶAPIサーバーです。サーバー側でHTMLを組み立てる用途にもJinja2テンプレートで対応でき、HTMLを返すルートでも入力の型変換・検証は働きます。response_class=HTMLResponse を指定したルートに /p/x のような不正な値を送ると422が返り、OpenAPIにも text/html の応答として記載されます。管理画面やORMまで必要なら、Djangoを比較対象に入れます。
最新版と動作要件(2026年10月時点)
| 項目 | 値 |
|---|---|
| FastAPI最新版 | 0.162.0(2026-10-23) |
| 必要なPython | 3.10以上 |
| Starlette | 0.46.0以上(最新1.10.0) |
| Pydantic | 2.9.0以上(最新2.14.1) |
| ライセンス | MIT |
値はPyPIのfastapiのページで確認したものです。Python 3.9のサポートは0.129.0(2026-02-12)で終わりました。注意したいのは、Python 3.9の環境で pip install fastapi を実行してもエラーにならない点です。Python 3.9の仮想環境で実行すると、Python 3.9に対応する最後の版である0.128.8が選ばれます。通常のpip出力には選択・インストールされた版が表示されるため、その表示も確認してください。記事のコードが動かないときは、まず python -c "import fastapi; print(fastapi.__version__)" で実際に入った版を確かめてください。
Pydanticはv2系が前提です。v1系の @validator や parse_obj() はv2でも非推奨APIとして残っており、警告付きで動く場合があります。新規コードでは、それぞれ @field_validator と model_validate() を使います。v1との違いはPydanticとは?読み方・BaseModelの使い方とV1との違い【2.13対応】で整理しています。
FastAPIのインストールと開発サーバーの起動
pip install “fastapi[standard]” と3通りの入れ方
仮想環境を有効にしてから、標準の依存関係込みでインストールします。公式ドキュメントは uv add "fastapi[standard]" の形で書いていますが、pipでも同じです。
pip install "fastapi[standard]"
引用符は省略できません。zshでは角括弧がファイル名のパターンとして解釈されるため、囲まないとインストールが失敗します。
入れ方は3通りあり、入るものが大きく違います。次の表は、Python 3.12の空の仮想環境にFastAPI 0.162.0を入れた際のパッケージ数です。OSや依存パッケージの更新によって個数は変わるため、固定の内訳ではありません。
| 指定 | パッケージ数 | fastapiコマンド | Uvicorn |
|---|---|---|---|
fastapi |
11 | 起動には追加依存が必要 | なし |
fastapi[standard-no-fastapi-cloud-cli] |
53 | あり | あり |
fastapi[standard] |
59 | あり | あり |
standard には、Uvicorn・httpx・Jinja2・python-multipartに加えて、0.142.0で追加された標準のOpenTelemetry対応のためのopentelemetry-sdkとエクスポーターが入ります。さらに fastapi-cli[standard] を経由して、商用のデプロイ先FastAPI Cloud向けの fastapi-cloud-cli と、それが依存する sentry-sdk も入ります。本番のコンテナイメージに外部サービス向けのCLIを含めたくないなら、fastapi[standard-no-fastapi-cloud-cli] を指定すれば差分の6個が入りません。素の fastapi だけではUvicornが入らず、fastapiコマンドによる起動にも追加依存が必要です。直接起動する場合はUvicornを別に入れます。
main.pyの最小コードとfastapi devによる起動
main.py を作ります。
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"Hello": "World"}
@app.get("/items/{item_id}")
def read_item(item_id: int, q: str | None = None):
return {"item_id": item_id, "q": q}
同じディレクトリで起動します。ファイル名の指定は不要です。
fastapi dev
fastapi-cli 0.0.32で実行すると、次の行が表示されました。main.py から app を見つけ、自動リロード付きのUvicornを起動しています。
🐍 Using import string: main:app (auto-discovered, use --verbose to learn more)
🌐 Server started at http://127.0.0.1:8000
Documentation at http://127.0.0.1:8000/docs
INFO: Will watch for changes in these directories: ['/private/tmp/v3859/app']
INFO: Started reloader process [76282] using WatchFiles
http://127.0.0.1:8000/items/5?q=somequery を開くと {"item_id": 5, "q": "somequery"} が返り、/docs にSwagger UI、/redoc にReDocが表示されます。main.py 以外の名前にする場合や、プロジェクトに複数のアプリがある場合は、起動ログが案内するとおり pyproject.toml に [tool.fastapi] と entrypoint = "main:app" を書いておけば毎回の指定が要らなくなります。
以前から使われてきた uvicorn main:app --reload も今も動きます。公式チュートリアルの標準手順が fastapi dev に変わっただけで、Uvicornのオプションを細かく指定したい場合や、プロセス管理を別のツールに任せる場合は直接起動で構いません。Uvicorn自体の設定はUvicornとは?読み方・Gunicornとの違いと起動・本番設定【0.54対応】で解説しています。
fastapi dev と fastapi run の違い
2つのコマンドは同じUvicornを起動しますが、既定値が開発用と本番用に分かれています。両方を起動したときの表示と既定値は次のとおりです。
| 項目 | fastapi dev | fastapi run |
|---|---|---|
| 起動時の表示 | development mode | production mode |
| 待ち受けアドレス | 127.0.0.1(自分のPCだけ) | 0.0.0.0(外部から届く) |
| 自動リロード | 有効 | 無効 |
| 複数プロセス | 使わない | --workers で指定 |
fastapi dev のままコンテナに入れると、127.0.0.1でしか待ち受けないため、コンテナの外からは接続できません。本番では fastapi run を使うか、Uvicornをリロードなしで直接起動します。開発用Dockerでは fastapi dev --host 0.0.0.0 も使えます。リバースプロキシの下のパスで公開する場合は、0.146.0で非推奨になった FastAPI(root_path=...) の代わりに fastapi run --root-path /api/v1 を指定します。
パラメータとリクエストボディの書き方
パスパラメータとクエリパラメータの振り分け
上のコードでは、item_id はURLパスの {item_id} と同じ名前なのでパスパラメータになり、パスに現れない q はクエリパラメータになります。どちらに置くかを書き分ける必要はありません。
型ヒントは変換のルールでもあります。/items/5 の "5" は整数の5に変換され、/items/abc を開くと次のJSONとともにHTTP 422が返りました。どの引数が不正なのかが loc に入るため、エラー処理を自分で書く必要はありません。
{"detail": [{"type": "int_parsing", "loc": ["path", "item_id"],
"msg": "Input should be a valid integer, unable to parse string as an integer",
"input": "abc"}]}
Pydanticモデルによるリクエストボディの検証
POSTで受け取るJSONは、Pydanticモデルを引数の型にするだけで検証されます。
from fastapi import FastAPI
from pydantic import BaseModel, Field
app = FastAPI()
class Item(BaseModel):
name: str
price: float = Field(gt=0)
tax: float | None = None
@app.post("/items/")
def create_item(item: Item):
total = item.price + (item.tax or 0)
return {"name": item.name, "total": total}
{"name": "a", "price": 0} を送ると、greater_than の422で弾かれ、関数の中には入りません。{"name": "a", "price": 100, "tax": 10} なら {"name": "a", "total": 110.0} が返ります。このモデルはそのまま /docs のリクエストボディ定義にもなるため、型の定義・検証・ドキュメントを1か所で管理できます。
エラー応答・起動時の処理・バックグラウンド処理
実際のAPIでよく使う3つの機能を1つにまとめた例です。見つからないときに404を返す HTTPException、各ワーカーのアプリ起動時と終了時に実行される lifespan、レスポンスを返した後に処理を続ける BackgroundTasks を使っています。
from contextlib import asynccontextmanager
from fastapi import BackgroundTasks, FastAPI, HTTPException
fake_db: dict[int, str] = {}
@asynccontextmanager
async def lifespan(app: FastAPI):
fake_db[1] = "alice" # ワーカーごとに起動時に1回(DB接続の確立など)
yield
fake_db.clear() # ワーカーごとに終了時に1回(接続のクローズなど)
app = FastAPI(lifespan=lifespan)
def write_log(message: str):
with open("access.log", "a") as f:
f.write(message + "\n")
@app.get("/users/{user_id}")
def read_user(user_id: int, background_tasks: BackgroundTasks):
if user_id not in fake_db:
raise HTTPException(status_code=404, detail="User not found")
background_tasks.add_task(write_log, f"read {user_id}")
return {"user_id": user_id, "name": fake_db[user_id]}
TestClientで確かめると、/users/1 は200で {"user_id": 1, "name": "alice"}、/users/2 は404で {"detail": "User not found"} を返し、access.log には read 1 の1行だけが書かれました。古い記事にある @app.on_event("startup") は非推奨なので、新しく書くなら lifespan を使います。BackgroundTasks は同じプロセスの中で動くため、数分かかる処理や失敗時の再実行が必要な処理には向きません。その場合はCeleryなどのタスクキューに任せます。
ファイルが増えてきたら APIRouter でルーターを分けて app.include_router() でまとめ、ブラウザのSPAから呼ぶなら CORSMiddleware を登録します。APIRouter は fastapi から、CORSMiddleware は fastapi.middleware.cors からimportします。追加のインストールは要りません。ディレクトリ構成の決め方はFastAPIのベストプラクティス|ディレクトリ構成・SQLAlchemy 2.1の設計・テストを実測で解説で扱っています。
async def と def の使い分け:迷ったら def
同期I/Oを呼ぶ場所は、FastAPIの並行処理性能を左右します。「FastAPIは非同期だから async def で書く」という理解は誤りで、場所を間違えるとかえって遅くなります。公式のasyncの解説は、await できないライブラリ(多くのDBライブラリがこれに当たる)を使うなら普通の def で書き、判断がつかないときも def を使うよう勧めています。
| 関数の中身 | 宣言 | 実行される場所 |
|---|---|---|
| await で呼ぶ非同期ライブラリ | async def | イベントループ |
| await できない同期ライブラリ | def | 外部スレッドプール |
| どこにも通信しない軽い計算 | async def | イベントループ |
| 判断がつかない | def | 外部スレッドプール |
差がどれくらい出るかを確かめるため、0.5秒の待ちを入れた3つのエンドポイントを fastapi run(ワーカー1つ)で起動し、httpxの非同期クライアントから10件を同時に送りました。各エンドポイントに10件を asyncio.gather で送り、全応答が返るまでを time.perf_counter で測っています。環境はmacOS 26.6.2・Intel Core i9-9880H・Python 3.12・Uvicorn 0.54.0・httpx 0.28.1で、2回実行した結果が後の表です。
import asyncio
import time
from fastapi import FastAPI
app = FastAPI()
@app.get("/bad")
async def bad():
time.sleep(0.5) # 同期の待ちを async def の中で呼ぶ
return {"ok": True}
@app.get("/good")
def good():
time.sleep(0.5) # def ならスレッドプールで動く
return {"ok": True}
@app.get("/best")
async def best():
await asyncio.sleep(0.5) # 非同期の待ちを await する
return {"ok": True}
送信側のスクリプトは次のとおりです。
import asyncio
import time
import httpx
async def main():
async with httpx.AsyncClient(timeout=60) as c:
for path in ["/bad", "/good", "/best"]:
t = time.perf_counter()
await asyncio.gather(
*[c.get(f"http://127.0.0.1:8000{path}") for _ in range(10)]
)
print(path, f"{time.perf_counter() - t:.2f}s")
asyncio.run(main())
| エンドポイント | 1回目 | 2回目 |
|---|---|---|
| /bad(async def+同期の待ち) | 6.43秒 | 6.03秒 |
| /good(def+同期の待ち) | 0.75秒 | 0.72秒 |
| /best(async def+await) | 0.60秒 | 0.59秒 |
/bad では、time.sleep() がイベントループを止めている間ほかのリクエストが一切進まず、10件が1件ずつ順に処理されました。/good は同じ同期処理でも、FastAPIが def の関数を外部スレッドプールで動かすため並行に進みます。psycopg2での問い合わせや requests.get() を async def の中に書くと、/bad と同じことが起きます。
スレッドプールにも上限はあります。AnyIOの既定は40で、0.162.0の環境で anyio.to_thread.current_default_thread_limiter().total_tokens を表示しても40です。数秒かかる同期処理を大量に def で流すとプールが埋まり、後のリクエストが待たされます。重いCPU処理は async def に置いても同じくイベントループを止めるので、別のワーカーやタスクキューに逃がしてください。この規則は依存関係(Depends)にもそのまま当てはまり、同期のDBセッションを返す依存は def で書きます。
0.140〜0.162で変わった挙動:古いコードが動かなくなる箇所
FastAPIは2026年7月の0.139.0から10月23日の0.162.0まで、3か月あまりで60本のリリースを重ねました。0.x系のため、マイナー番号の更新でも動作が変わります。リリースノートで「Breaking Changes」とされたもののうち、アプリの応答が変わるものを、0.139.0・0.150.0・0.162.0の3つの版で同じコードを動かして比べました。
| 変更が入った版 | 書き方 | 0.139.0 | 0.162.0 |
|---|---|---|---|
| 0.151.0 | Body()に既定値10、JSONでnullを送る | 200・10に置換 | 422 |
| 0.155.0 | Query(custom=”v”) | 動く | TypeError |
| 0.146.0 | FastAPI(root_path=”/api”) | 動く | 非推奨の警告 |
| 0.147.0→0.162.0 | 必須のbool型フォームを省略 | 422 | 422(0.150.0は200・False) |
最も気づきにくいのは1行目です。limit: Annotated[int, Body()] = 10 と note: Annotated[str | None, Body()] = "x" を受け取るエンドポイントに {"limit": null, "note": null} を送ると、0.139.0と0.150.0はエラーを出さずに {"limit": 10, "note": "x"} を返しました。送られた null が既定値に置き換わっていたということです。0.162.0では limit が int_type の422になり、note は None として受け取られます。「値を消したい」という意味で null を送るクライアントがある場合、更新の前後で保存される値が変わります。
2行目は、型に関係しない独自の値をドキュメントに出すためのキーワード引数が0.155.0で削除されたものです。Query(json_schema_extra={"custom": "v"}) に書き換えます。4行目のHTMLフォームのチェックボックスは、0.147.0で「省略されたらFalse」に変わり、0.162.0で元の挙動に戻りました。agree: Annotated[bool, Form()] = False のように既定値Falseを明示しておけば、どの版でも同じ結果になります。
このほか、0.142.0でOpenTelemetryに標準で対応し、0.143.0からは OTEL_* 環境変数によるエクスポーターの自動設定に FASTAPI_OTEL_AUTO_CONFIGURE=true が必要になりました。0.139.0以前から更新する場合は、版を1つに固定したうえでテストを通してから上げてください。fastapi>=0.100 のような下限だけの指定は、上の変更を知らないうちに取り込みます。
FlaskやDjangoとの違いと、FastAPIを選ぶべきでない場面
判断の軸は、作るものがAPIだけかどうかです。FastAPIを除いたDjangoとFlaskの比較はDjangoとFlaskの違い|内蔵範囲・非同期・案件規模から選ぶ判断基準で扱っています。
| 観点 | FastAPI | Flask | Django |
|---|---|---|---|
| 主な用途 | API(JSON) | 小規模なWeb全般 | フルスタックWeb |
| 入力検証 | 標準(Pydantic) | 拡張で追加 | Forms・DRF |
| APIドキュメント | 自動生成 | 拡張で追加 | DRFで追加 |
| ORM | なし | なし | あり |
| 管理画面 | なし | なし | あり |
| 非同期 | ASGIで標準 | 一部対応 | 一部対応 |
FastAPIを選ぶべきでないのは、管理画面・ユーザー認証・ORM・マイグレーションが最初から必要な業務システムです。FastAPIでこれらをそろえると、SQLAlchemy・Alembic・認証ライブラリを1つずつ選んで接続することになり、Djangoが最初から持っているものを作り直す形になります。サーバー側でHTMLを組み立て、管理画面やORMも必要なWebサイトでは、それらを標準搭載するDjangoを優先して検討します。
逆に、Flaskで作ったAPIに手書きの入力チェックとSwaggerの定義がたまっているなら、移行する価値があります。どちらもデコレータでルートを書く構造なので、@app.route("/x", methods=["POST"]) を @app.post("/x") に、手書きのチェックをPydanticモデルに置き換える作業が中心です。HTMLテンプレートを返す画面が中心のFlaskアプリは、移行しても得るものはほとんどありません。TypeScriptのHonoと比べる場合はHono vs FastAPI(Python)の違い|Honoとは・メリット・使い方を実行結果で比較【Hono 4.13】が参考になります。
本番環境で踏みやすい落とし穴
既定で公開される /docs と /openapi.json
Swagger UIとOpenAPIスキーマは既定で誰でも見られる状態で公開されます。社内向けのAPIでも、/openapi.json を開けば全エンドポイントの仕様が読めます。本番では FastAPI(docs_url=None, redoc_url=None, openapi_url=None) で無効にするか、認証の内側に置きます。docs_url=None だけでは画面が消えるだけで、/openapi.json は200のまま残ります。手順はFastAPIのSwagger UI(/docs)の使い方|表示・設定・本番で隠す手順【2026年版】で解説しています。
fastapi run のワーカー数とコンテナ
複数のCPUコアを使うには、ワーカー数を指定します。
fastapi run --workers 4 main.py
ただしKubernetesのようにコンテナを複数並べる環境では、公式のDockerの解説はワーカーを使わず、コンテナごとにUvicornのプロセスを1つだけ動かす構成を勧めています。プロセスの数はコンテナの数で調整し、1つのコンテナの中で二重に増やさないという考え方です。Dockerfileの書き方はFastAPI×ReactのDocker本番デプロイ|uv対応Dockerfileとcompose構成にまとめています。サーバーを持たずに動かす選択肢としては、Cloudflare Python Workersとは?FastAPIをエッジで動かす手順とパッケージ制約【2026年10月時点】もあります。
DB・フロントエンド接続の実装範囲
FastAPIはORMを持たないため、データベースにはSQLAlchemyなどを組み合わせます。同期のSQLAlchemyを使うなら、前の章のとおりパス関数と依存は def で書きます。Reactなどのフロントエンドと組み合わせる場合のCORSと開発用プロキシの設定はReact×FastAPI連携の手順|CORS・Viteプロキシ・型生成とDocker Composeで動かす【2026年版】で扱っています。既存システムとのつなぎ込みまで含めて外部に任せたい場合は、一創のAPI開発・システム連携で相談を受け付けています。
よくある質問
FastAPIとREST APIの違いは何ですか?
REST APIはHTTPでやり取りするAPIの設計スタイルの名前で、FastAPIはREST APIをPythonで作るためのフレームワークです。比べる対象ではなく、FastAPIでREST APIを作るという関係です。
FastAPIのライセンスは何ですか?商用利用できますか?
MITライセンスです。商用のサービスに組み込むことも、ソースを公開せずに使うこともできます。著作権表示とライセンス文を残す義務はあります。土台のPydanticはMIT、StarletteはBSD-3-Clauseで、どちらも商用利用を妨げない許諾型のライセンスです。
fastapi dev と uvicorn main:app –reload のどちらを使えばよいですか?
開発中は公式の標準である fastapi dev を使います。中身は自動リロード付きのUvicornなので、uvicorn main:app --reload でも同じように動きます。Uvicornのオプションを細かく指定したいときだけ直接起動を選びます。
FastAPIのパス関数はすべて async def で書く必要がありますか?
必要ありません。await できないライブラリ(多くの同期DBドライバ)を使う関数は def で書きます。async def の中で同期処理を呼ぶと、10件同時のリクエストで def の8倍以上の時間がかかりました。
FastAPIに脆弱性はありますか?
FastAPI本体に加えて、土台のStarletteの脆弱性もFastAPIのアプリに影響します。2026年には、不正なHostヘッダにより、request.url.pathを使う認可チェックを迂回されるCVE-2026-48710が公表されました。pip show starlette で実際に入っている版を確かめ、修正版まで上げてください。条件と修正版はStarletteの脆弱性CVE-2026-48710とは|Hostヘッダで認可を迂回される条件と修正手順で整理しています。