Pythonで高速なWeb APIを最速で立ち上げたいなら、FastAPIが最有力候補です。
本稿では、環境構築から最小のAPI起動、GET/POSTの実装、型ヒントと自動バリデーション、ルータ構成、エラーハンドリングまでを段階的に解説します。
初心者でも迷わず動かせる具体的な手順を重視し、実行コマンドやレスポンス例も丁寧に示します。
FastAPIとは?Pythonで高速なWeb APIを最速で作る
FastAPIはStarlette(ASGI)ベースで動くPythonのWebフレームワークで、型ヒントを活用した宣言的な記述と自動ドキュメント生成が最大の特徴です。
非同期I/Oに対応し、モダンな書き方で高い開発体験を提供します。
低ボイラープレートで生産性が高いため、初めてのWeb API開発にも適しています。
FastAPIの強み 高速かつモダンな書き方
FastAPIの速度はASGIサーバ(Uvicorn)とStarletteの組み合わせによるものです。
非同期処理(Async)に標準対応し、I/O待ちが多いAPIにおいて高いスループットを得られます。
Pythonの型ヒントに準拠した宣言設計により、IDE補完や静的解析と相性が良い点も魅力です。
以下は主な特長の一覧です。
特長 | 説明 |
---|---|
高速 | ASGI(Uvicorn)+Starletteで非同期I/Oに強い |
型ヒント駆動 | 引数と戻り値の型からリクエスト/レスポンスを自動検証 |
自動ドキュメント | OpenAPI仕様に基づいて/docs と/redoc を自動生成 |
バリデーション | Pydanticによる厳密なデータ検証と変換 |
モジュール性 | ルータ(APIRouter)でエンドポイントを整理しやすい |
モダンな標準(ASGI・型ヒント・OpenAPI)に寄り添った設計が、継続的な拡張・保守にも効いてきます。
型ヒントで直感的に書ける 入門に最適
FastAPIでは、関数の引数や戻り値にPythonの型ヒントを付けるだけで、自動的にリクエストのパースと検証が行われます。
たとえばitem_id: int
と書けば、文字列が送られても整数に変換を試み、無理ならバリデーションエラーを返してくれます。
コードがドキュメントになり、バグも減らせます。
# 型ヒントのイメージ例
def calc_price_with_tax(price: float, tax: float | None = None) -> float:
# 税率は省略可能(None)。指定がなければ0扱いにします。
t = tax or 0.0
return price * (1 + t)
自動ドキュメントで学びやすい OpenAPI対応
FastAPIはOpenAPI仕様のスキーマを自動生成します。
サーバを起動するだけで/docs
にSwagger UI、/redoc
にReDocが利用可能です。
エンドポイントとスキーマを確認しながら試せるため、学習効率とチームの連携効率が高まります。
# OpenAPI JSONを取得する例
curl -s http://127.0.0.1:8000/openapi.json | head -n 20
最速で作るHello Web API
ここでは、最小構成でAPIを立ち上げ、ブラウザとcurlで動作確認する手順を示します。
Python初心者を想定して、環境構築から順に進めます。
まずは動く体験を得ることが大切です。
FastAPIとUvicornのインストール
Python 3.10以上を推奨します。
仮想環境(venv)の利用をおすすめします。
必要最小限はfastapi
とASGIサーバuvicorn
です。
# (任意) 仮想環境の作成と有効化
python -m venv .venv
# Windows: .venv\Scripts\activate
# macOS/Linux:
source .venv/bin/activate
# FastAPI本体とUvicornをインストール
pip install fastapi uvicorn[standard]
最小のmain.pyを作成する
最小のエントリポイントmain.py
を作ります。
まずはルート/
だけにHelloを返すAPIです。
# main.py
from fastapi import FastAPI
# アプリケーション本体を作成。タイトルやバージョンはOpenAPIにも反映されます。
app = FastAPI(title="Hello FastAPI", version="0.1.0")
# GET / にアクセスしたときにJSONを返す
@app.get("/")
async def read_root():
# FastAPIでは辞書を返すとJSONに自動変換されます
return {"message": "Hello, FastAPI!"}
uvicornで起動する コマンド例
直接python main.py
で実行してもAPIサーバーは立ち上がりません。
Uvicornでアプリを起動します。
モジュール名:アプリ変数の順で指定します。
uvicorn main:app --port 8000
# 代表的な起動ログ(環境により差異あり)
INFO: Started server process [12345]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
ブラウザで確認する ルートと/docs
ブラウザでhttp://127.0.0.1:8000/
にアクセスすると、HelloのJSONが表示されます。

ドキュメントはhttp://127.0.0.1:8000/docs
ですぐ確認できます。
リクエストをその場で送れるTry it outも便利です。
# ルートの動作確認
curl -s http://127.0.0.1:8000/
{"message":"Hello, FastAPI!"}
GETエンドポイントを追加する
パスパラメータとオプションのクエリ文字列を扱う例です。
型ヒントだけで自動バリデーションが効きます。
# main.py (追記)
from typing import Optional
@app.get("/items/{item_id}")
async def read_item(item_id: int, q: Optional[str] = None):
# item_id は自動で整数にバリデートされます
result = {"item_id": item_id}
if q is not None:
result["query"] = q
return result
# 呼び出し例
curl -s "http://127.0.0.1:8000/items/42?q=fast"
{"item_id":42,"query":"fast"}
POSTでJSONを受け取る 基本の書き方
Pydanticモデルでリクエストボディの型と検証を定義します。
必須・最小値などの制約は<Field>で宣言的に指定できます。
# main.py (追記)
from pydantic import BaseModel, Field
# リクエストボディのスキーマ定義
class Item(BaseModel):
name: str = Field(..., min_length=1, description="商品名")
price: float = Field(..., ge=0, description="税抜価格")
tax: float | None = Field(None, ge=0, description="税率(0〜)")
@app.post("/items")
async def create_item(item: Item):
# 税率が省略された場合は0とみなします
tax = item.tax or 0.0
price_with_tax = item.price * (1 + tax)
# レスポンスは辞書でもPydanticモデルでもOK
return {
"name": item.name,
"price_with_tax": round(price_with_tax, 2),
}
# 正常なPOST
curl -s -X POST "http://127.0.0.1:8000/items" \
-H "Content-Type: application/json" \
-d '{"name":"Notebook","price":1000,"tax":0.1}'
{"name":"Notebook","price_with_tax":1100.0}
# priceに文字列など不正な値を送ると422エラー(自動バリデーション)
curl -s -X POST "http://127.0.0.1:8000/items" \
-H "Content-Type: application/json" \
-d '{"name":"X","price":"free"}'
{
"detail": [
{
"type": "float_parsing",
"loc": ["body", "item", "price"],
"msg": "Input should be a valid number, unable to parse string as a number",
"input": "free"
}
]
}
実用に向けた基本テクニック
ここからは、現場で役立つ基本機能と設計のコツを解説します。
小さく始めて、整理しながら拡張するのが成功パターンです。
型ヒントと自動バリデーションの基本
FastAPIはPydanticを使い、型に基づく厳密な検証と自動変換を行います。
最小・最大、文字列長、正規表現などを宣言的に指定できます。
# 型と制約の例
from pydantic import BaseModel, Field
class User(BaseModel):
username: str = Field(..., min_length=3, max_length=20)
age: int = Field(..., ge=0, le=130)
email: str | None = Field(None, pattern=r".+@.+") # 簡易なパターン例
@app.post("/users")
async def create_user(user: User):
# user は検証済みデータ。型が保証され、安心して使えます
return {"ok": True, "user": user}
# 不正データの例
curl -s -X POST "http://127.0.0.1:8000/users" \
-H "Content-Type: application/json" \
-d '{"username":"ab","age":-1,"email":"invalid"}'
{
"detail": [
{
"type": "string_too_short",
"loc": ["body","user","username"],
"msg": "String should have at least 3 characters"
},
{
"type": "greater_than_or_equal",
"loc": ["body","user","age"],
"msg": "Input should be greater than or equal to 0"
},
{
"type": "string_pattern_mismatch",
"loc": ["body","user","email"],
"msg": "String should match pattern '.+@.+'"
}
]
}
失敗は早く、理由は詳しく返るため、クライアント側の実装も進めやすくなります。
パスパラメータとクエリパラメータの扱い
パスは必須、クエリはオプションが基本です。
Path/Queryヘルパで範囲や説明を宣言できます。
from fastapi import Path, Query
@app.get("/users/{user_id}")
async def get_user(
user_id: int = Path(..., ge=1, description="ユーザID(1以上)"),
keyword: str | None = Query(None, min_length=2, max_length=50, description="検索キーワード"),
limit: int = Query(10, ge=1, le=100, description="最大件数(1〜100)")
):
return {
"user_id": user_id,
"keyword": keyword,
"limit": limit,
}
curl -s "http://127.0.0.1:8000/users/7?keyword=hi&limit=5"
{"user_id":7,"keyword":"hi","limit":5}
入力仕様はコードに近い場所に書くことで、実装とドキュメントの乖離を防げます。
レスポンスモデルで返却データを定義
レスポンスの型もモデルで固定できます。
返すフィールドを明示し、不要情報を遮断するのに有効です。
from pydantic import BaseModel
from typing import List
class ItemIn(BaseModel):
name: str
price: float
tax: float | None = None
internal_note: str | None = None # クライアントに返したくない内部情報
class ItemOut(BaseModel):
id: int
name: str
price: float
tax: float | None = None
tags: List[str] = []
fake_db = []
id_seq = 0
@app.post("/catalog/items", response_model=ItemOut, response_model_exclude_none=True)
async def add_item(item: ItemIn):
global id_seq
id_seq += 1
# DBに保存するときは内部情報も保持できるが、レスポンスには含めない
stored = {"id": id_seq, **item.model_dump(), "tags": []}
fake_db.append(stored)
# response_modelにより、返却はItemOutに整形されます
return stored
curl -s -X POST "http://127.0.0.1:8000/catalog/items" \
-H "Content-Type: application/json" \
-d '{"name":"Pen","price":120,"tax":0.1,"internal_note":"secret"}'
{"id":1,"name":"Pen","price":120.0,"tax":0.1,"tags":[]}
レスポンスモデルはそのAPIの契約であり、後方互換性を保つ助けになります。
ファイル構成とルータでコードを整理
規模が大きくなったら、APIRouterでエンドポイントを分割します。
機能単位(items, usersなど)で整理するのが定石です。
app/
├─ main.py
├─ routers/
│ ├─ items.py
│ └─ users.py
└─ schemas.py
# app/schemas.py
from pydantic import BaseModel, Field
class ItemCreate(BaseModel):
name: str = Field(..., min_length=1)
price: float = Field(..., ge=0)
class Item(BaseModel):
id: int
name: str
price: float
# app/routers/items.py
from fastapi import APIRouter, HTTPException
from ..schemas import ItemCreate, Item
router = APIRouter(prefix="/items", tags=["items"])
# 簡易インメモリDB
_db: list[Item] = []
_seq = 0
@router.post("", response_model=Item)
async def create_item(payload: ItemCreate):
global _seq
_seq += 1
item = Item(id=_seq, name=payload.name, price=payload.price)
_db.append(item)
return item
@router.get("/{item_id}", response_model=Item)
async def get_item(item_id: int):
for it in _db:
if it.id == item_id:
return it
raise HTTPException(status_code=404, detail="Item not found")
# app/routers/users.py
from fastapi import APIRouter
router = APIRouter(prefix="/users", tags=["users"])
@router.get("")
async def list_users():
return [{"id": 1, "name": "alice"}]
# app/main.py
from fastapi import FastAPI
from .routers import items, users
app = FastAPI(title="Modular FastAPI")
# ルータを登録してURLを合体
app.include_router(items.router)
app.include_router(users.router)
# ルートは任意
@app.get("/")
async def root():
return {"message": "OK"}
# ルータを含むアプリを起動
uvicorn app.main:app --port 8000
URLの共通prefixやタグでドキュメントも整理でき、大規模化しても見通しが良くなります。
開発を速くする自動リロード(–reload)
開発時は--reload
を付けると、コード変更のたびにサーバが自動再起動します。
ミスにすぐ気づけ、反復速度が上がります。
uvicorn app.main:app --reload
INFO: Uvicorn running on http://127.0.0.1:8000
INFO: Detected file change in 'app/routers/items.py'. Reloading...
本番運用では–reloadは使わない点に注意しましょう。
開発専用の便利機能です。
エラーハンドリングの基本とHTTP例外
見つからない・権限なしといった状況には、HTTPExceptionで適切なステータスコードを返します。
独自例外のハンドリングも可能です。
from fastapi import HTTPException, Request
from fastapi.responses import JSONResponse
# 既存のルータ/アプリのどこかに追記
class OutOfStockError(Exception):
def __init__(self, item_id: int):
self.item_id = item_id
@app.get("/buy/{item_id}")
async def buy(item_id: int):
# 在庫ゼロの例(実際はDBで判定)
if item_id == 999:
raise OutOfStockError(item_id)
if item_id < 0:
raise HTTPException(status_code=400, detail="Invalid item id")
return {"status": "purchased", "item_id": item_id}
# 独自例外ハンドラ
@app.exception_handler(OutOfStockError)
async def out_of_stock_handler(request: Request, exc: OutOfStockError):
return JSONResponse(
status_code=409, # 競合(CONFLICT)
content={"error": "out_of_stock", "item_id": exc.item_id},
)
# 404/400/409など適切なステータスが返る
curl -s -i "http://127.0.0.1:8000/buy/999" | head -n 20
HTTP/1.1 409 Conflict
content-type: application/json
content-length: 37
{"error":"out_of_stock","item_id":999}
クライアントが対処しやすい形で失敗を設計することが、堅牢なAPIの鍵です。
HTTPステータスとエラー構造の一貫性を保ちましょう。
まとめ
FastAPIは高速・宣言的・自動ドキュメントという3拍子がそろった、Python初心者にも扱いやすいWeb APIフレームワークです。
最小構成で動かし、GET/POSTで基礎を押さえ、型ヒントとレスポンスモデルで契約を明確化し、ルータで整理すれば、小さく作ってスムーズに拡張できます。
最後に本番運用では–reloadを使わない、適切なHTTPステータスで失敗を伝えるなどの基本も忘れずに、堅牢なAPI開発を進めてください。
OpenAPIドキュメント/docs
を活用しつつ、まずは本稿のサンプルから手を動かしてみましょう。