21 人が閲覧(直近 30 日) Python

FastAPIとは?最短の起動手順とasync/defの使い分け、0.162の変更点

FastAPIとは?最短の起動手順とasync/defの使い分け、0.162の変更点

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ヘッダで認可を迂回される条件と修正手順で整理しています。

関連記事

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

一創の公式 SNS

X では毎朝の新着記事と IT ニュースの速報を、YouTube・TikTok・Instagram・LinkedIn・Facebook では記事の解説動画を配信しています。

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

ほか 14 件の記事からもリンクされています。

資料請求

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

  1. 2026.11.09 テックブログ エディオン不正アクセス第2報:カード番号の対象確認とログ・決済画面の改ざん対策
  2. 2026.11.09 テックブログ LANSCOPE不正アクセス第2報:製品への影響と偽連絡の確認、停止中のライセンス認証の窓口
  3. 2026.11.10 テックブログ Claude Fable 5.2とは:料金・破壊的変更4点とFable 5.1からの移行手順【2026年11月】
  4. 2026.11.09 テックブログ ステップゴルフ不正アクセス:口座情報の対象確認と偽連絡・引き落としの注意点
  5. 2026.11.10 テックブログ ニトリの情報漏洩:最大3,762万件の対象確認となりすまし連絡の見分け方

RELATED POSTS 関連記事

目次