FastAPI 完全ガイド【2026年最新】|Python 3.13 対応・Pydantic v2・async/await・Dependency Injection・OpenAPI 自動生成・SQLAlchemy 2.0 Async・OAuth2/JWT 認証・テスト・本番デプロイまでを実戦パターンで解説

FastAPI 完全ガイド【2026年最新】|Python 3.13 対応・Pydantic v2・async/await・Dependency Injection・OpenAPI 自動生成・SQLAlchemy 2.0 Async・OAuth2/JWT 認証・テスト・本番デプロイまでを実戦パターンで解説 Python

FastAPI は「Python の型ヒントをそのままリクエスト検証と OpenAPI 自動生成に使う」発想で、2020 年の登場から数年でPython Web フレームワークの事実上の標準になりました。Starlette(ASGI)と Pydantic(データ検証)を基盤に、Node.js や Go に匹敵するスループット型ヒントによる開発速度 2〜3 倍ドキュメント自動生成という三拍子が揃っています。

2026 年 4 月時点では Python 3.13Pydantic v2.7+SQLAlchemy 2.0 AsyncAnnotated 型Lifespan events が標準構成で、fastapi[standard] パッケージ 1 つで CLI(fastapi dev / fastapi run)・Uvicorn・httpx・email-validator がまとめてインストールされます。Pydantic v1 系のサポートは後方互換モードのみで、新規プロジェクトは必ず v2 前提です。

この記事では、Path Operations(GET/POST/PUT/DELETE)、パラメータの種類、Pydantic v2 モデルと Annotated、Depends による依存性注入、OAuth2 + JWT 認証、Background Tasks、WebSocket、CORS、Lifespan、SQLAlchemy 2.0 Async 連携、TestClient での自動テスト、OpenAPI 自動生成、Uvicorn / Docker / FastAPI Cloud でのデプロイ、推奨プロジェクト構造、落とし穴までを実戦コード付きで網羅的に解説します。

スポンサーリンク

2026 年 4 月時点の前提バージョン

コンポーネント 推奨バージョン 要点
Python 3.12 / 3.13 3.9 のサポートは 2026 年 2 月の FastAPI 0.130.0 で打ち切り。3.13 の PEP 695 型パラメータ構文が使える
FastAPI 0.130+ 系 Pydantic v1 系は後方互換で残るが、新規は v2 必須。fastapi[standard] で CLI と依存一式が入る
Pydantic 2.7+ v2 は Rust 製の pydantic-corev1 比 5〜50 倍高速model_config による設定、Annotated 推奨
SQLAlchemy 2.0+ Async(AsyncSession / create_async_engine)が標準。DeclarativeBase + Mapped + mapped_column の 2.0 スタイル
ASGI サーバー Uvicorn 0.30+ --workers で複数プロセス、--reload で開発時リロード。Hypercorn / Granian も可
新規プロジェクトの鉄板スタック: Python 3.13 + fastapi[standard] + Pydantic 2.7+ + SQLAlchemy 2.0 Async + uv(パッケージマネージャ)+ ruff(リンタ/フォーマッタ)+ pytest(テスト)。依存管理は pyproject.toml 一枚、Docker は python:3.13-slim ベースが定番です。

インストールと最小アプリ

uv / pip でのセットアップ
# uv(2025 年以降の Python 標準マネージャ候補)
uv init my-api && cd my-api
uv add "fastapi[standard]>=0.130"
uv add "sqlalchemy[asyncio]>=2.0" "asyncpg>=0.30" "alembic>=1.13"
uv add --dev "pytest" "httpx" "ruff"

# 従来の pip でも同等
python -m venv .venv && source .venv/bin/activate
pip install "fastapi[standard]>=0.130" "sqlalchemy[asyncio]>=2.0" asyncpg alembic
pip install --upgrade pip

# 実行(開発モード: 自動リロード付き)
fastapi dev app/main.py

# 本番モード
fastapi run app/main.py
app/main.py ── 最小アプリ
from fastapi import FastAPI

app = FastAPI(
    title="My API",
    version="1.0.0",
    description="FastAPI with Python 3.13",
)

@app.get("/")
async def root() -> dict[str, str]:
    return {"message": "hello fastapi"}

@app.get("/health")
async def health() -> dict[str, str]:
    return {"status": "ok"}
動作確認
fastapi dev app/main.py
#  Serving at: http://127.0.0.1:8000
#  API docs: http://127.0.0.1:8000/docs

# OpenAPI は自動生成
curl http://127.0.0.1:8000/openapi.json | jq .

Path Operations ── ルーティングの基本

RESTful な CRUD エンドポイント
from fastapi import FastAPI, status
from pydantic import BaseModel

app = FastAPI()

class Post(BaseModel):
    id: int
    title: str
    body: str

# 擬似 DB
posts: dict[int, Post] = {}

@app.get("/posts", response_model=list[Post])
async def list_posts() -> list[Post]:
    return list(posts.values())

@app.get("/posts/{post_id}", response_model=Post)
async def get_post(post_id: int) -> Post:
    return posts[post_id]

@app.post("/posts", response_model=Post, status_code=status.HTTP_201_CREATED)
async def create_post(post: Post) -> Post:
    posts[post.id] = post
    return post

@app.put("/posts/{post_id}", response_model=Post)
async def update_post(post_id: int, post: Post) -> Post:
    posts[post_id] = post
    return post

@app.delete("/posts/{post_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_post(post_id: int) -> None:
    posts.pop(post_id, None)

ルーター分割(APIRouter)

app/api/posts.py ── 機能単位でファイル分割
from fastapi import APIRouter

router = APIRouter(prefix="/posts", tags=["posts"])

@router.get("/")
async def list_posts():
    return []

@router.get("/{post_id}")
async def get_post(post_id: int):
    return {"id": post_id}
app/main.py でまとめて include
from fastapi import FastAPI
from app.api import posts, users, auth

app = FastAPI()
app.include_router(posts.router)
app.include_router(users.router)
app.include_router(auth.router, prefix="/auth", tags=["auth"])

パラメータ 5 種類 ── Path / Query / Body / Header / Cookie

typing.Annotated で全種類を統一的に扱う(2026 年の推奨スタイル)
from typing import Annotated
from fastapi import FastAPI, Path, Query, Body, Header, Cookie
from pydantic import BaseModel, Field

app = FastAPI()

class Item(BaseModel):
    name: Annotated[str, Field(min_length=1, max_length=100)]
    price: Annotated[float, Field(gt=0, description="0 より大きい")]
    tags: list[str] = []

@app.post("/items/{category}")
async def create_item(
    # Path パラメータ: 正規表現・範囲・例
    category: Annotated[str, Path(pattern="^[a-z-]+$", examples=["books", "toys"])],

    # Query パラメータ: 範囲・別名・複数値
    q: Annotated[str | None, Query(min_length=1, max_length=50)] = None,
    limit: Annotated[int, Query(ge=1, le=100)] = 20,
    tags: Annotated[list[str] | None, Query(alias="tag")] = None,

    # Body: Pydantic モデル
    item: Annotated[Item, Body()] = ...,

    # Header: HTTP ヘッダ(アンダースコア → ハイフン変換)
    x_request_id: Annotated[str | None, Header()] = None,

    # Cookie
    session: Annotated[str | None, Cookie()] = None,
):
    return {
        "category": category, "q": q, "limit": limit,
        "tags": tags, "item": item,
        "x_request_id": x_request_id, "session": session,
    }
Annotated 推奨の理由: 2026 年の FastAPI は「Annotated[型, メタデータ]」形式を推奨しています。デフォルト引数の位置に制約オブジェクトを書く旧スタイル(Query(default=..., min_length=...))は将来的に非推奨になる可能性があります。Annotated なら、同じ型を別の関数でも再利用できるため、保守性が大きく向上します。

Pydantic v2 モデル ── 検証と OpenAPI の両輪

Pydantic v2 の典型的な書き方
from datetime import datetime
from typing import Annotated
from pydantic import BaseModel, EmailStr, Field, ConfigDict, field_validator

class UserBase(BaseModel):
    model_config = ConfigDict(
        str_strip_whitespace=True,
        json_schema_extra={
            "examples": [{"email": "alice@example.com", "name": "Alice"}],
        },
    )
    email: EmailStr
    name: Annotated[str, Field(min_length=1, max_length=50)]

class UserCreate(UserBase):
    password: Annotated[str, Field(min_length=8, max_length=72)]

    @field_validator("password")
    @classmethod
    def password_strength(cls, v: str) -> str:
        if v.isalpha() or v.isdigit():
            raise ValueError("英数字混在が必要")
        return v

class UserRead(UserBase):
    id: int
    created_at: datetime
    # SQLAlchemy モデルからの変換を許可(旧 orm_mode=True)
    model_config = ConfigDict(from_attributes=True)
v1 からの主な違い:orm_mode = Truemodel_config = ConfigDict(from_attributes=True)、② @validator@field_validator@classmethod 必須)、③ .dict().model_dump()、④ .json().model_dump_json()、⑤ parse_objmodel_validate。Pydantic v1 から移行する場合は bump-pydantic CLI で大半が自動変換されます。

Dependency Injection ── Depends で共通ロジックを抽出

関数ベースの依存性
from typing import Annotated
from fastapi import Depends, Header, HTTPException, status

async def require_api_key(
    x_api_key: Annotated[str | None, Header()] = None,
) -> str:
    if not x_api_key:
        raise HTTPException(status_code=401, detail="X-API-Key missing")
    if x_api_key != "expected":
        raise HTTPException(status_code=403, detail="invalid key")
    return x_api_key

# 型エイリアスでさらに簡潔に
ApiKey = Annotated[str, Depends(require_api_key)]

@app.get("/secure")
async def secure_endpoint(api_key: ApiKey):
    return {"api_key": api_key}
クラスベース依存性 ── 共通パラメータ集約
class Pagination:
    def __init__(
        self,
        page: Annotated[int, Query(ge=1)] = 1,
        size: Annotated[int, Query(ge=1, le=100)] = 20,
    ):
        self.page = page
        self.size = size
        self.offset = (page - 1) * size

PaginationDep = Annotated[Pagination, Depends()]

@app.get("/items")
async def list_items(pg: PaginationDep):
    return {"page": pg.page, "size": pg.size, "offset": pg.offset}
グローバル依存性 ── すべてのパスで検査
from fastapi import FastAPI, Depends

async def log_request():
    print("request received")

# アプリ全体に適用
app = FastAPI(dependencies=[Depends(log_request)])

# 特定ルーターにだけ適用も可能
# router = APIRouter(dependencies=[Depends(require_api_key)])
依存性の再利用メリット: Depends は戻り値の型がそのまま引数の型になるため、IDE 補完と型チェックが完全に効きます。同じ get_current_user を 50 エンドポイントで使っても、ログイン確認ロジックは 1 か所に集約され、変更もテストも容易です。

OAuth2 + JWT 認証の実装

app/core/security.py ── 共通認証ロジック
from datetime import datetime, timedelta, timezone
from typing import Annotated
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from jose import JWTError, jwt
from passlib.context import CryptContext
from pydantic import BaseModel

SECRET_KEY = "change-me-in-env"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/token")

class TokenPayload(BaseModel):
    sub: str
    exp: datetime

def hash_password(raw: str) -> str:
    return pwd_context.hash(raw)

def verify_password(raw: str, hashed: str) -> bool:
    return pwd_context.verify(raw, hashed)

def create_access_token(subject: str) -> str:
    expire = datetime.now(timezone.utc) + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    payload = {"sub": subject, "exp": expire}
    return jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)

async def get_current_user(
    token: Annotated[str, Depends(oauth2_scheme)],
) -> str:
    credentials_exc = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="invalid token",
        headers={"WWW-Authenticate": "Bearer"},
    )
    try:
        data = TokenPayload(**jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]))
    except JWTError:
        raise credentials_exc
    return data.sub

CurrentUser = Annotated[str, Depends(get_current_user)]
app/api/auth.py ── ログインと保護エンドポイント
from fastapi import APIRouter, Depends, HTTPException
from fastapi.security import OAuth2PasswordRequestForm
from typing import Annotated

from app.core.security import (
    verify_password, create_access_token, CurrentUser,
)

router = APIRouter(prefix="/auth", tags=["auth"])

@router.post("/token")
async def login(
    form: Annotated[OAuth2PasswordRequestForm, Depends()],
) -> dict[str, str]:
    # form.username / form.password を DB 照合(例示)
    user = fake_users_db.get(form.username)
    if not user or not verify_password(form.password, user["hashed_password"]):
        raise HTTPException(status_code=401, detail="invalid credentials")
    return {
        "access_token": create_access_token(subject=form.username),
        "token_type": "bearer",
    }

@router.get("/me")
async def me(user: CurrentUser) -> dict[str, str]:
    return {"username": user}
SECRET_KEY は必ず環境変数から: ソースに直書きしたキーで JWT を署名すると、コードが漏れた瞬間に全セッションが偽造可能になります。本番では pydantic-settings.env から読み込み、Docker / K8s なら Secrets Manager や Sealed Secrets を使ってください。GitHub Actions での OIDC 経由デプロイなら GitHub Actions 完全ガイドの OIDC セクションを参照してください。

Background Tasks ── レスポンス後の非同期処理

メール送信などの I/O を即座に返す
from fastapi import BackgroundTasks

async def send_welcome_mail(email: str, name: str) -> None:
    # 実際は SMTP やサードパーティ API を呼ぶ
    print(f"send to {email} ({name})")

@app.post("/signup")
async def signup(
    email: str,
    name: str,
    bg: BackgroundTasks,
):
    # DB 保存など同期で終わらせる
    user = await db.user.create(email=email, name=name)
    # レスポンス送信後に実行される
    bg.add_task(send_welcome_mail, email=email, name=name)
    return {"id": user.id}
軽い非同期処理向け: Background Tasks は同じワーカープロセス内でレスポンス後に実行されます。「メール 1 通送る」「Slack 通知する」程度の軽い I/O に最適。重い処理や再試行が必要なら Celery / Dramatiq / arq のタスクキューを使い、BackgroundTasks でそれらのキューに enqueue するだけに留めます。

WebSocket ── リアルタイム通信

チャットの最小実装
from fastapi import FastAPI, WebSocket, WebSocketDisconnect

app = FastAPI()

class ConnectionManager:
    def __init__(self) -> None:
        self.active: list[WebSocket] = []

    async def connect(self, ws: WebSocket) -> None:
        await ws.accept()
        self.active.append(ws)

    def disconnect(self, ws: WebSocket) -> None:
        self.active.remove(ws)

    async def broadcast(self, message: str) -> None:
        for ws in list(self.active):
            await ws.send_text(message)

manager = ConnectionManager()

@app.websocket("/ws")
async def chat(ws: WebSocket) -> None:
    await manager.connect(ws)
    try:
        while True:
            msg = await ws.receive_text()
            await manager.broadcast(msg)
    except WebSocketDisconnect:
        manager.disconnect(ws)
本番で注意すべき点: ① Uvicorn はプロセスをまたいだ接続共有ができないため、複数 workers 構成だとマネージャが分離します → Redis Pub/Sub で中継する、② スケール要件が重いなら Socket.IO 互換の python-socketio + Redis 構成に移行、③ Cloudflare / ALB などアイドルタイムアウトで切断されるので ping/pong を実装してください。

CORS と Middleware

CORS 設定
from fastapi.middleware.cors import CORSMiddleware

app.add_middleware(
    CORSMiddleware,
    allow_origins=[
        "https://app.example.com",
        "http://localhost:3000",
    ],
    allow_credentials=True,
    allow_methods=["GET", "POST", "PUT", "DELETE"],
    allow_headers=["*"],
)
カスタムミドルウェア ── リクエスト ID 発行
import uuid
from fastapi import Request

@app.middleware("http")
async def add_request_id(request: Request, call_next):
    request_id = request.headers.get("x-request-id") or uuid.uuid4().hex
    request.state.request_id = request_id
    response = await call_next(request)
    response.headers["x-request-id"] = request_id
    return response
allow_origins=["*"]allow_credentials=True は同時に使えません: CORS 仕様で禁じられています。Cookie を使う認証(セッション)なら必ず具体的なオリジンを列挙、JWT Authorization ヘッダー認証ならワイルドカードでも可ですが本番ではワイルドカード禁止が基本です。

Lifespan Events ── 起動 / 終了フック

旧 @on_event(“startup/shutdown”) の置換
from contextlib import asynccontextmanager
from fastapi import FastAPI
from sqlalchemy.ext.asyncio import create_async_engine

engine = None   # 後で格納

@asynccontextmanager
async def lifespan(app: FastAPI):
    # startup
    global engine
    engine = create_async_engine("postgresql+asyncpg://user:pw@db/app")
    print("DB connected")
    yield
    # shutdown
    await engine.dispose()
    print("DB closed")

app = FastAPI(lifespan=lifespan)

SQLAlchemy 2.0 Async ── 非同期 DB 連携

app/db/database.py ── 接続の初期化
from sqlalchemy.ext.asyncio import (
    AsyncSession, async_sessionmaker, create_async_engine,
)
from sqlalchemy.orm import DeclarativeBase

DATABASE_URL = "postgresql+asyncpg://user:pw@localhost:5432/app"

engine = create_async_engine(DATABASE_URL, echo=False, pool_pre_ping=True)
AsyncSessionLocal = async_sessionmaker(engine, expire_on_commit=False)

class Base(DeclarativeBase):
    pass

async def get_db():
    async with AsyncSessionLocal() as session:
        yield session
app/db/models.py ── 2.0 スタイルのモデル
from datetime import datetime
from sqlalchemy import String, DateTime, func
from sqlalchemy.orm import Mapped, mapped_column

from app.db.database import Base

class User(Base):
    __tablename__ = "users"

    id:         Mapped[int]       = mapped_column(primary_key=True)
    email:      Mapped[str]       = mapped_column(String(255), unique=True, index=True)
    name:       Mapped[str]       = mapped_column(String(50))
    hashed_pw:  Mapped[str]       = mapped_column(String(72))
    created_at: Mapped[datetime]  = mapped_column(DateTime(timezone=True), server_default=func.now())
app/api/users.py ── CRUD エンドポイント
from typing import Annotated
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from app.db.database import get_db
from app.db.models import User
from app.schemas.user import UserCreate, UserRead
from app.core.security import hash_password

router = APIRouter(prefix="/users", tags=["users"])
DB = Annotated[AsyncSession, Depends(get_db)]

@router.post("/", response_model=UserRead, status_code=201)
async def create_user(payload: UserCreate, db: DB):
    user = User(
        email=payload.email,
        name=payload.name,
        hashed_pw=hash_password(payload.password),
    )
    db.add(user)
    await db.commit()
    await db.refresh(user)
    return user

@router.get("/", response_model=list[UserRead])
async def list_users(db: DB):
    result = await db.execute(select(User).order_by(User.id))
    return result.scalars().all()

@router.get("/{user_id}", response_model=UserRead)
async def get_user(user_id: int, db: DB):
    user = await db.get(User, user_id)
    if not user:
        raise HTTPException(status_code=404, detail="not found")
    return user
Alembic でマイグレーション: SQLAlchemy 2.0 + Async なら Alembic も env.py を async 版にして使います。alembic init -t async migrations で async 対応の雛形が生成されます。TS 側で Drizzle を使う場合は TypeScript × Drizzle ORM 完全ガイド と比較すると理解が深まります。

テスト ── TestClient と pytest

tests/test_items.py ── 同期テスト
from fastapi.testclient import TestClient
from app.main import app

client = TestClient(app)

def test_root():
    r = client.get("/")
    assert r.status_code == 200
    assert r.json() == {"message": "hello fastapi"}

def test_create_item():
    r = client.post("/posts", json={"id": 1, "title": "t", "body": "b"})
    assert r.status_code == 201
    data = r.json()
    assert data["title"] == "t"
tests/test_async_db.py ── 非同期テスト(httpx AsyncClient)
import pytest
from httpx import AsyncClient, ASGITransport
from app.main import app

@pytest.mark.asyncio
async def test_async_endpoint():
    transport = ASGITransport(app=app)
    async with AsyncClient(transport=transport, base_url="http://test") as ac:
        r = await ac.get("/users/")
        assert r.status_code == 200
conftest.py ── テスト用 DB のフィクスチャ
import pytest_asyncio
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine, async_sessionmaker

from app.db.database import Base, get_db
from app.main import app

TEST_DB_URL = "postgresql+asyncpg://test:test@localhost:5432/test"

@pytest_asyncio.fixture
async def db_session():
    engine = create_async_engine(TEST_DB_URL)
    async with engine.begin() as conn:
        await conn.run_sync(Base.metadata.drop_all)
        await conn.run_sync(Base.metadata.create_all)
    Session = async_sessionmaker(engine, expire_on_commit=False)

    async def override_get_db():
        async with Session() as s:
            yield s

    app.dependency_overrides[get_db] = override_get_db
    yield
    app.dependency_overrides.clear()
    await engine.dispose()

OpenAPI 自動生成と CLI 連携

FastAPI はコードを書いた時点で OpenAPI / Swagger UI / ReDoc を自動生成します。フロントエンド側はスキーマから型を自動生成して、クライアントとバックエンドの型整合性を担保できます。

エンドポイント 用途
/docs Swagger UI(対話的 API 試行)
/redoc ReDoc(読みやすいドキュメント)
/openapi.json OpenAPI スキーマ JSON(CLI / 型生成ツール向け)
TS クライアント型を自動生成
# バックエンド起動中に openapi-typescript で取得
npx openapi-typescript http://localhost:8000/openapi.json -o frontend/src/api/schema.ts

# tRPC 風に使うなら openapi-fetch
# https://openapi-ts.dev/openapi-fetch/
TS 側での活用: React 19 / Nuxt 4 / Astro のどれを使っても、OpenAPI スキーマから TypeScript 型を自動生成すれば「バックエンドが変わったらフロントが型エラーで気づく」構成が完成します。スキーマファースト開発は Claude Code × API 開発自動化完全ガイド 相当の記事も参考になります。

デプロイ ── Uvicorn / Docker / FastAPI Cloud

Uvicorn 直接起動(小〜中規模)

マルチワーカーでプロダクション起動
# 開発
fastapi dev app/main.py

# 本番(--workers = CPU コア数 x 2 + 1 が目安)
fastapi run app/main.py --workers 4 --host 0.0.0.0 --port 8000

# systemd 化するならサービスファイルを用意
# /etc/systemd/system/my-api.service
# [Service]
# ExecStart=/opt/venv/bin/fastapi run /opt/app/main.py --workers 4
# Restart=always

Docker(python:3.13-slim ベース)

Dockerfile
# syntax=docker/dockerfile:1.7
FROM python:3.13-slim AS base

ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1 \
    PIP_NO_CACHE_DIR=1

WORKDIR /app

# 依存のみ先に入れてレイヤキャッシュを効かせる
COPY pyproject.toml uv.lock ./
RUN pip install uv && uv sync --frozen --no-dev

COPY app app

EXPOSE 8000
CMD ["uv", "run", "fastapi", "run", "app/main.py", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]
docker-compose.yml ── API + PostgreSQL
services:
  api:
    build: .
    ports: ["8000:8000"]
    environment:
      DATABASE_URL: postgresql+asyncpg://app:pw@db:5432/app
      SECRET_KEY: ${SECRET_KEY}
    depends_on: [db]

  db:
    image: postgres:17-alpine
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: pw
      POSTGRES_DB: app
    volumes: ["pgdata:/var/lib/postgresql/data"]
    ports: ["5432:5432"]

volumes:
  pgdata:

FastAPI Cloud(公式マネージドサービス)

fastapi deploy で即公開
# fastapi[standard] に同梱
fastapi deploy
# ログイン → プロジェクト選択 → 自動デプロイ
# 環境変数・ドメイン・SSL まで自動

Kubernetes / サーバーレス

Docker イメージ化すれば ECS Fargate・GKE・AWS Lambda(Mangum 経由)・Cloud Run・Fly.io などどこでも動きます。CI/CD は GitHub Actions 完全ガイド の OIDC パターンを使って長期シークレットなしで運用するのが 2026 年標準です。

推奨プロジェクト構造(中〜大規模)

app/ を中心に機能単位で分ける
my-api/
├── pyproject.toml
├── Dockerfile
├── docker-compose.yml
├── .env.example
├── alembic.ini
├── migrations/                 # Alembic
├── app/
│   ├── main.py                 # FastAPI インスタンス & ルーター include
│   ├── core/
│   │   ├── config.py           # pydantic-settings
│   │   ├── security.py         # JWT / パスワードハッシュ
│   │   └── logging.py
│   ├── db/
│   │   ├── database.py         # engine / session
│   │   ├── models.py           # SQLAlchemy 2.0 モデル
│   │   └── base.py
│   ├── schemas/                # Pydantic v2 モデル(入出力 DTO)
│   │   ├── user.py
│   │   └── post.py
│   ├── api/
│   │   ├── deps.py             # 共通依存性
│   │   ├── auth.py
│   │   ├── users.py
│   │   └── posts.py
│   ├── services/               # ビジネスロジック(ルーターと DB の中間)
│   └── worker/                 # Celery / arq タスク
└── tests/
    ├── conftest.py
    ├── test_auth.py
    └── test_users.py

落とし穴と注意点

async 関数内で同期 I/O を呼ぶ

async エンドポイント内で requests.get(...) や同期 sqlalchemy.Session を使うと、イベントループがブロックされて性能が出ません。外部 API は httpx.AsyncClient、DB は AsyncSession を使います。どうしても同期ライブラリを使う場合は await asyncio.to_thread(fn) で別スレッドに逃がします。

Pydantic モデルをそのまま DB モデルにしない

Pydantic と SQLAlchemy を兼用するライブラリ(SQLModel 等)は便利ですが、Create / Read / Update で必要なフィールドが異なるため、DTO を分離するのが結果的に保守性が高くなります。schemas/user.pyUserCreate / UserUpdate / UserRead を定義し、DB モデルは SQLAlchemy に寄せるのが 2026 年の定石です。

BackgroundTasks に重い処理を積む

Background Tasks は同じワーカー内で実行されます。CPU 集中や数十秒かかる処理を積むと、そのワーカーが次のリクエストを処理できなくなる状態が発生します。重い処理は Celery / arq / Dramatiq に逃がし、Background Tasks はキューへの enqueue だけを担わせます。

DB セッションの取り回しミス

get_db の戻り値を複数リクエストで共有してはいけません。Depends の yield は「リクエスト単位のライフサイクル」を保証するため、必ず async def get_db(): async with AsyncSessionLocal() as s: yield s の形で使います。モジュール変数として保持すると、コネクションリークとトランザクション分離境界崩壊が同時に起きます。

OAuth2PasswordBearer の tokenUrl とルータ prefix

OAuth2PasswordBearer(tokenUrl="/auth/token") の URL は実際のログインエンドポイントと完全一致する必要があります。Router に prefix="/auth" を付けていたら /auth/token、API の全体プレフィックス /api/v1 を付けていたら /api/v1/auth/token。ここがズレると Swagger UI の Authorize ボタンが動きません。

本番 UvicornWorker と Gunicorn の混在

2026 年は Uvicorn 単独の --workers オプションで十分です。かつて推奨された gunicorn -k uvicorn.workers.UvicornWorker は、Uvicorn 側が multi-process を標準サポートした現在は不要fastapi run --workers 一本で OK です。

よくある質問

QFastAPI と Flask / Django はどう違いますか?
AFastAPI は型ヒント駆動+async ネイティブ+OpenAPI 自動生成が売りで、API サーバー専門。Flask は軽量で柔軟だが手動で型検証・ドキュメント生成を行う必要があり、Django は Admin・ORM・Auth・テンプレートが揃ったフルスタック。2026 年は「API バックエンド専用なら FastAPI」「管理画面まで 1 つで済ませたいなら Django Ninja(Django + FastAPI 風)または Django REST framework」が主流の住み分けです。
QFastAPI と TS の Hono はどう使い分けますか?
APython / TS のどちらを主言語にするかが最大の基準です。Python エコシステムのデータ処理・ML・LLM 連携を重視するなら FastAPI、TS フロントエンドと型を共有し同一ランタイムで書きたいなら Hono。どちらも OpenAPI を自動生成でき、エッジ・サーバーレス・Docker のいずれでも動くため、言語選択 = エコシステム選択として判断するのが現実的です。
QPydantic v1 プロジェクトを v2 に移行する手順は?
Apip install bump-pydantic で自動変換スクリプトを導入、② bump-pydantic app/ で実行(orm_mode → from_attributes@validator → @field_validator 等を変換)、③ 手動で .dict() → .model_dump() / parse_obj → model_validate などの残件を修正、④ pytest で全テスト実行。FastAPI 側は Pydantic v1 と v2 の同時稼働も可能なので段階移行ができます(from pydantic.v1 import BaseModel as V1BaseModel)。
QSQLAlchemy 2.0 の Async は必須ですか?
A新規プロジェクトは Async 一択です。FastAPI は ASGI で async イベントループ上で動くため、同期 SQLAlchemy を混ぜるとワーカーがブロックされて性能が出ませんcreate_async_engine + AsyncSession を基本にし、どうしても同期ライブラリに依存する箇所は asyncio.to_thread で分離します。Drizzle / Prisma との比較は TypeScript × Drizzle ORM 完全ガイドClaude Code × データベース開発完全ガイド を参照。
QOpenAPI スキーマを使ってフロント型を自動生成するには?
Aopenapi-typescript / orval / hey-api などのツールで、/openapi.json から TS 型と fetch クライアントを生成できます。CI に組み込めば「バックエンドのエンドポイントを変えるとフロントがビルド失敗する」構成になり、タイポや契約破壊を自動検出できます。React 19 / Nuxt 4 / Astro のいずれのフロントでも同じパターンで使えます。
QBackground Tasks と Celery / arq の使い分けは?
A数秒以内・冪等・単プロセス内で良いなら FastAPI 標準の BackgroundTasks失敗リトライが必要・長時間処理・複数ワーカー間で分散・スケジュール実行が必要なら Celery(伝統的)か arq(Redis ベース・async ネイティブ・軽量)を導入します。2026 年の新規プロジェクトでは arq が選ばれるケースが増えています。
QFastAPI で GraphQL を書くには?
Astrawberry-graphql が最有力です。FastAPI のルーターに /graphql エンドポイントを生やし、Pydantic と同じ型ヒントで GraphQL スキーマを書けます。ただし 2026 年はtRPC / OpenAPI ベースの型共有が主流になっており、GraphQL を選ぶ理由は「複数クライアントからの多様なクエリ要件」がある場合に限定されつつあります。
Q本番で監視・ログはどう構成しますか?
A定番は「Prometheus + Grafana」または「OpenTelemetry + Datadog / Jaeger / Sentry」。FastAPI 向けには prometheus-fastapi-instrumentator、OpenTelemetry は opentelemetry-instrumentation-fastapi。リクエスト ID をログに出力し、トレースを ASGI ミドルウェアで自動差し込みすると運用時の障害調査が劇的に楽になります。エラー監視は Sentry が定番で、sentry-sdk[fastapi] を入れるだけです。

まとめ

  • 2026 年の鉄板スタック: Python 3.13 + fastapi[standard] + Pydantic 2.7+ + SQLAlchemy 2.0 Async
  • Path Operations + APIRouterで機能単位にファイル分割。tags で OpenAPI の分類が自動整理
  • パラメータは Annotated[型, Path/Query/Body/Header/Cookie]に統一するのが 2026 年推奨スタイル
  • Pydantic v2: Rust 製コアで 5〜50× 高速。from_attributes=True@field_validator.model_dump()
  • Depends による依存性注入で認証・DB セッション・共通パラメータを DRY 化。型エイリアスで読みやすく
  • OAuth2 + JWT: OAuth2PasswordBearer + jose + passlib で実装。SECRET は環境変数から
  • Background Tasks は軽量用途、重いものは Celery / arq に逃がす
  • SQLAlchemy 2.0 Async: AsyncSession + DeclarativeBase + Mapped の 2.0 スタイル、Alembic も async 版
  • OpenAPI 自動生成で TS フロントに型を配信。openapi-typescript で型同期
  • Uvicorn –workers + Docker + GitHub Actions OIDC が標準的本番構成

TS 側のバックエンド選択肢は TypeScript × Hono 完全ガイド、データ層は TypeScript × Drizzle ORM 完全ガイドClaude Code × データベース開発完全ガイド、BaaS は Claude Code × Supabase フルスタック開発完全ガイド、フロントエンドは React 19Nuxt 4AstroSvelte 5、CI/CD は GitHub Actions 完全ガイド、バッチ認証の安全化は Oracle Wallet(SEPS)完全ガイド もあわせて、FastAPI を核に据えた 2026 年型 Python バックエンド構成を組み上げてください。