FastAPIとuvで始めるPythonモダンAPI開発

# はじめに

Python 製の Web API フレームワークである FastAPI をご存じでしょうか。
近年、FastAPI はさまざまなサービスで活用されており、その使いやすさや性能が注目されています。
私自身も学習を兼ねて、どのようなフレームワークなのか体験してみたいと思いました。

また、Python のパッケージマネージャとして話題になっていた `uv` も併せて導入してみて、
FastAPI + uv + 認証 + Pydantic バリデーション + pytest によるテストという構成で簡単な API を構築してみました。

今回作成したものこちら

https://github.com/y-inoue15/fastapi-tutorial

# FastAPI とは？

FastAPI は Python 製の Web フレームワークの一つで、以下のような特徴があります。

- 型ヒントを活用した 自動バリデーション
- OpenAPI（Swagger）仕様に準拠したドキュメント自動生成
- `async`/`await` による 非同期処理への対応
- Pydantic による データモデル定義の簡素化

近年では、FastAPI は Python の Web API 開発におけるデファクトスタンダードの一つとなりつつあります。
その人気は GitHub のスター数にも表れており、2025 年 6 月時点での主要フレームワークのスター数は以下の通りです。

[FastAPI (fastapi/fastapi)](https://github.com/fastapi/fastapi)　→　**86,400 スター**
[Flask (pallets/flask)](https://github.com/pallets/flask)　→　69,800 スター

FastAPI は 2018 年 12 月に最初のリリースが行われて以降、わずか数年で Flask を上回るスター数を獲得しています。
FastAPI が Python Web API 開発のデファクトスタンダードとなっていることが分かります。

# uv とは？

uv は Python 用の超高速パッケージマネージャです。
Poetry や pip の代替として注目されており、依存関係の解決がとても速いという特徴があります。
今回は `uv` を使って仮想環境の作成やパッケージインストールを行ってみました。

https://github.com/astral-sh/uv

# 環境構築

※ uv が未導入の方は、[公式](https://docs.astral.sh/uv/getting-started/installation/)のインストールガイドを参考に導入してください。

```bash
# 初期化
uv init

# ライブラリのインストール
uv add fastapi[standard]
uv add --dev ruff pytest pytest-cov

# Windows の場合
.venv\Scripts\activate.bat
# macOS/Linux の場合
source .venv/bin/activate
```

# ディレクトリ構成

```
fastapi-tutorial/
├── main.py                # FastAPIアプリ本体（エントリポイント）
├── src/
│   ├── auth.py            # 認証のロジック
│   ├── models.py          # Pydanticによるデータモデル
│   ├── router.py          # ルーティング（エンドポイント定義）
│   └── __init__.py
├── tests/
│   ├── test_auth.py       # 認証ロジックのテスト
│   ├── test_models.py     # モデルのバリデーションテスト
│   ├── test_router.py     # APIエンドポイントのテスト
│   └── __init__.py
├── pyproject.toml
├── uv.lock
├── README.md
└── ...
```

- `main.py` で `src/router.py` を FastAPI アプリに組み込む構成です
- 認証・モデル・ルーティングは `src/` ディレクトリに分離しています
- テストは `tests/` ディレクトリにまとめています

# テスト

今回は実運用も見据えてテストコードを作成しました。

- ルートエンドポイントの正常系/異常系
- `/items/{item_id}` の認証・バリデーション・クエリパラメータ
- `/items` POST のバリデーション（必須/型/値域/異常系）
- 認証（有効/無効/スキーム不正/トークンなし等）
- モデルのバリデーション（Pydantic）
- エラーハンドリング（404, 405, 422 など）

# 実装

## auth.py

FastAPI の `Depends` を活用して、ベアラー形式の認証を担うモジュールです。
今回は JWT の内容を本格的に検証するというよりも、構文チェックとモック検証の形で実装しています。

```python:auth.py
from fastapi import Depends, HTTPException
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials

security = HTTPBearer()

def verify_token(credentials: HTTPAuthorizationCredentials = Depends(security)):
    token = credentials.credentials
    if token != "mocked-jwt-token":
        raise HTTPException(status_code=401, detail="Invalid token")
    return token
```

- `security = HTTPBearer()` でベアラー認証の仕組みを用意し、`verify_token`関数でトークンの検証を行います
- 今回は「mocked-jwt-token」という文字列のみを有効なトークンとしています

## models.py

Pydantic を使ったデータモデル定義です。

```python:models.py
from pydantic import BaseModel, Field

class Item(BaseModel):
    name: str = Field(..., min_length=1)
    price: float = Field(..., gt=0)

class ItemResponse(BaseModel):
    item_id: int
    name: str
    price: float
    q: str | None = None
```

- `Item`はリクエストボディ用、`ItemResponse`は API レスポンス用のモデルです
- バリデーション（例：name は 1 文字以上、price は 0 より大きい）も Pydantic で簡潔に記述できます

## router.py

FastAPI アプリのルーティング（エンドポイント定義）をまとめたファイルです。

```python:router.py
from fastapi import APIRouter, Depends, Path, Query
from .auth import verify_token
from .models import Item, ItemResponse

router = APIRouter()

@router.get("/")
async def read_root():
    return {"message": "Welcome to the FastAPI application!"}

@router.get("/items/{item_id}", response_model=ItemResponse)
async def read_item(
    item_id: int = Path(..., gt=0),
    q: str | None = Query(None, max_length=50),
    _: str = Depends(verify_token),
):
    item = {"name": f"item{item_id}", "price": 100.0}
    return ItemResponse(item_id=item_id, name=item["name"], price=item["price"], q=q)

@router.post("/items", response_model=ItemResponse)
async def create_item(item: Item, _: str = Depends(verify_token)):
    return ItemResponse(item_id=1, name=item.name, price=item.price)
```

- `/items/{item_id}` でアイテム取得、`/items` でアイテム作成ができます
- どちらも認証（ベアラートークン）が必要です
- レスポンス型やバリデーションも自動で行われます

## サーバーの起動方法

FastAPI アプリの開発サーバーは、公式ガイドに従い以下のコマンドで起動できます。

```bash
uv run fastapi dev
```

このコマンドは依存関係の解決・仮想環境の作成・開発サーバーの起動を一括で行います。
本番環境では、以下のコマンドを使用します。

```bash
uv run fastapi run
```

FastAPI CLI の詳細は公式ドキュメントを参照してください。

https://fastapi.tiangolo.com/fastapi-cli/

https://docs.astral.sh/uv/guides/integration/fastapi/

# API ドキュメントの確認

サーバー起動後、以下の URL にアクセスすると自動生成された API ドキュメント（Swagger UI）を確認できます。

[http://localhost:8000/docs](http://localhost:8000/docs) （Swagger UI）
[http://localhost:8000/redoc](http://localhost:8000/redoc) （ReDoc）

# 動作確認
では、実際に作成したものを動作確認してみます。
今回は Swagger UI で動作確認します。

## 正常系

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1240861/3f7aaa11-6742-4a05-a4b0-51f24a1000f6.png)

## 不正なトークン

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1240861/8ee6aa47-99da-492d-be3a-44a452c9a49b.png)

## 不正なボディ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1240861/bf71d36f-0bb8-4dda-a72c-dcfe3eb8edfd.png)

## 存在しない ID

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1240861/d9c74368-1236-4f84-8ba9-714f03f268fa.png)

# pytest によるテスト

ここでは `tests/test_router.py` で定義したエンドポイントの動作確認のテストを抜粋して紹介します。

```python:test_router.py
from fastapi.testclient import TestClient
from main import app

client = TestClient(app)

def test_root_endpoint_success():
    """ルートエンドポイントが正常に動作することを確認"""
    response = client.get("/")
    assert response.status_code == 200
    data = response.json()
    assert data["message"] == "Welcome to the FastAPI application!"


def test_get_item_with_valid_token():
    """有効なトークンでアイテム取得が正常に動作することを確認"""
    headers = {"Authorization": "Bearer mocked-jwt-token"}
    response = client.get("/items/1", headers=headers)
    assert response.status_code == 200
    data = response.json()
    assert data["item_id"] == 1
    assert data["name"] == "item1"
    assert data["price"] == 100.0
    assert data["q"] is None

def test_create_item_with_valid_token_and_data():
    """有効なトークンと正しいデータでアイテム作成が正常に動作することを確認"""
    headers = {"Authorization": "Bearer mocked-jwt-token"}
    payload = {"name": "test item", "price": 50.0}
    response = client.post("/items", json=payload, headers=headers)
    assert response.status_code == 200
    data = response.json()
    assert data["item_id"] == 1
    assert data["name"] == "test item"
    assert data["price"] == 50.0
    assert data["q"] is None
```

- ルートエンドポイントの正常系
- `/items/{item_id}` の認証・バリデーション
- `/items` POST のバリデーション

テスト実行：

```bash
uv run pytest
```

# まとめ

今回、FastAPI でのAPI開発からuv を活用した環境構築・認証・バリデーション・pytest テストという一連を試してみました。

- `uv` による爆速環境構築
- FastAPI の DI やバリデーションのシンプルさ
- テストのしやすさ（TestClient が便利）

これらの点が魅力的に感じました。

今回はまだ本格的な JWT 検証や DB 連携は行っていませんが、次はSQLModel や DB 連携、OAuth2 認証にも挑戦してみたいと思いました。

# 参考資料
https://qiita.com/tonnsama/items/a4010356fceca7235bba

https://qiita.com/abek21/items/7739163085899b257cb8

https://fastapi.tiangolo.com/learn/