10
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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
10
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?