FastAPIとは
FastAPIは、Pythonで高速なWeb APIを構築するためのモダンなフレームワーク。
Swagger UIが自動生成されるのがものすごく便利。型ヒント書くだけでバリデーションもやってくれる。
特徴:
- 高速: Starlette(非同期)+ Pydantic(バリデーション)の組み合わせ
- 型ヒント活用: 自動バリデーション、自動ドキュメント生成
- OpenAPI対応: Swagger UI が自動生成される
-
非同期対応:
async/awaitネイティブサポート
pip install fastapi uvicorn
最小限のAPI
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def root():
return {"message": "Hello World"}
起動:
uvicorn main:app --reload
http://localhost:8000/docs にアクセスすると、Swagger UIが自動生成されています!
Pydanticモデルで型安全なAPI
FastAPIの真価はPydanticとの連携にあります。
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field
from typing import Optional
from datetime import datetime
app = FastAPI(title="Todo API", version="1.0.0")
# リクエストモデル
class TodoCreate(BaseModel):
title: str = Field(..., min_length=1, max_length=100, description="タスクのタイトル")
description: Optional[str] = Field(None, max_length=500)
completed: bool = False
# レスポンスモデル
class TodoResponse(BaseModel):
id: int
title: str
description: Optional[str]
completed: bool
created_at: datetime
# インメモリDB
todos_db: dict[int, TodoResponse] = {}
next_id = 1
@app.post("/todos", response_model=TodoResponse, status_code=201)
async def create_todo(todo: TodoCreate):
"""新しいTodoを作成"""
global next_id
new_todo = TodoResponse(
id=next_id,
title=todo.title,
description=todo.description,
completed=todo.completed,
created_at=datetime.now()
)
todos_db[next_id] = new_todo
next_id += 1
return new_todo
バリデーションは自動!
# 空タイトル → 422 Unprocessable Entity
# title: "" は min_length=1 に違反
実行結果:
POST /todos {"title": ""}
→ 422 {"detail": [{"msg": "String should have at least 1 character"}]}
クエリパラメータとフィルタリング
from fastapi import Query
@app.get("/todos", response_model=list[TodoResponse])
async def list_todos(
completed: Optional[bool] = Query(None, description="完了状態でフィルタ"),
limit: int = Query(10, ge=1, le=100, description="取得件数")
):
"""Todo一覧を取得"""
todos = list(todos_db.values())
if completed is not None:
todos = [t for t in todos if t.completed == completed]
return todos[:limit]
使用例:
GET /todos?completed=true&limit=5
エラーハンドリング
@app.get("/todos/{todo_id}", response_model=TodoResponse)
async def get_todo(todo_id: int):
"""特定のTodoを取得"""
if todo_id not in todos_db:
raise HTTPException(status_code=404, detail="Todo not found")
return todos_db[todo_id]
依存性注入 (Dependency Injection)
FastAPIの最強機能。テスト容易性と再利用性が格段に上がります。
1. 共通パラメータの抽出
from fastapi import Depends
def common_parameters(q: Optional[str] = None, skip: int = 0, limit: int = 10):
"""共通のクエリパラメータ"""
return {"q": q, "skip": skip, "limit": limit}
@app.get("/items/")
async def read_items(commons: dict = Depends(common_parameters)):
return commons
2. クラスベースの依存性
class Pagination:
def __init__(self, page: int = 1, per_page: int = 10):
self.page = page
self.per_page = per_page
self.skip = (page - 1) * per_page
@app.get("/users/")
async def list_users(pagination: Pagination = Depends()):
return {
"page": pagination.page,
"per_page": pagination.per_page,
"skip": pagination.skip
}
GET /users/?page=3&per_page=25
→ {"page": 3, "per_page": 25, "skip": 50}
3. 認証の依存性チェーン
from fastapi import Header
async def verify_token(x_token: str = Header()):
if x_token != "secret-token":
raise HTTPException(status_code=401, detail="Invalid token")
return x_token
async def get_current_user(token: str = Depends(verify_token)):
# 本来はDBからユーザーを取得
return {"username": "testuser", "token": token}
@app.get("/protected/")
async def protected_route(user: dict = Depends(get_current_user)):
return {"message": f"Hello {user['username']}"}
実行結果:
GET /protected/ (トークンなし)
→ 422 Unprocessable Entity
GET /protected/ (x-token: wrong)
→ 401 {"detail": "Invalid token"}
GET /protected/ (x-token: secret-token)
→ 200 {"message": "Hello testuser"}
4. リソース管理(yield)
DB接続のように「使用後にクリーンアップが必要」な場合:
def get_db():
"""yield を使ったリソース管理"""
db = FakeDB() # 接続開始
try:
yield db # リクエスト処理に使用
finally:
db.close() # 必ずクリーンアップ
@app.get("/db-test/")
async def test_db(db: FakeDB = Depends(get_db)):
return {"db_connected": db.connected}
ログ:
DB接続開始
(リクエスト処理)
DB接続終了
TestClientでユニットテスト
from fastapi.testclient import TestClient
client = TestClient(app)
def test_create_todo():
response = client.post("/todos", json={
"title": "牛乳を買う",
"description": "低脂肪じゃないやつ"
})
assert response.status_code == 201
assert response.json()["title"] == "牛乳を買う"
def test_get_nonexistent_todo():
response = client.get("/todos/999")
assert response.status_code == 404
assert response.json()["detail"] == "Todo not found"
FlaskからFastAPIへの移行
| Flask | FastAPI |
|---|---|
@app.route("/", methods=["GET"]) |
@app.get("/") |
request.args.get("q") |
引数で q: str = None
|
request.json |
Pydanticモデルを引数に |
| Marshmallow | Pydantic(組み込み) |
| 同期のみ | async/await対応 |
| 手動でOpenAPI | 自動生成 |
なぜFastAPIを選ぶのか
速度比較(ベンチマーク)
- FastAPI: 約 15,000 req/sec
- Flask: 約 2,500 req/sec
(非同期I/O + Starlette/uvicornの恩恵)
開発体験
# Flaskの場合(型ヒントなし)
@app.route("/users", methods=["POST"])
def create_user():
data = request.json # 型不明
# 手動バリデーションが必要
if not data.get("name"):
return {"error": "name required"}, 400
...
# FastAPIの場合(型ヒント活用)
@app.post("/users", response_model=UserResponse)
async def create_user(user: UserCreate):
# バリデーション自動、型補完効く
...
まとめ
FastAPIを使うべき場面:
- 新規API開発: 型安全性と自動ドキュメントの恩恵
- 非同期処理が必要: I/Oバウンドな処理
- チーム開発: OpenAPI仕様で契約ファースト
Flaskを使い続ける場面:
- 既存のFlaskアプリ
- シンプルなプロトタイプ
2025年、新規でREST APIを作るならFastAPI一択と言っても過言ではありません。
# さあ始めよう
pip install fastapi uvicorn
uvicorn main:app --reload