FastAPIは飾りにすぎない——本当に必要なのはStarletteとPydanticだけ


[![Group193.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/f0ba3fa7-3929-499c-8db3-936b2ca8e961.png)](https://leapcell.io/)



**[Leapcell: サーバレス Web ホスティングのベスト](https://leapcell.io/)**


# Starlette と Pydantic：FastAPI を使わずに強力な API を構築する

Python の Web 開発の分野において、FastAPI はその簡潔で使いやすい機能のために多くの注目を集めています。しかし、実際には、FastAPI は Starlette と Pydantic の高レベルなカプセル化に過ぎません。[公式の Starlette ウェブサイト](https://www.starlette.io/)には、その豊富な機能が紹介されており、Pydantic はその強力なデータ検証機能で知られています。この 2 つのライブラリを直接使うことで、開発者は FastAPI のカプセル化に依存することなく、柔軟に高性能な API を構築することができます。次に、両者の核心的な機能と特徴を組み合わせて詳細に説明します。

## 1. Starlette の核心機能と例

### 1.1 非同期リクエスト処理
Starlette は ASGI 規格に基づいており、非同期タスクを効率的に処理することができます。FastAPI の書き方と比較すると、その下層のロジックがよりよく反映されます：

**FastAPI の例**
```python
from fastapi import FastAPI
import asyncio

# FastAPI アプリケーションのインスタンスを作成
app = FastAPI()

# デコレータを使って GET リクエストのルートを定義。この関数は非同期関数で、時間のかかる操作をブロッキングせずに処理できます
@app.get("/async_items/")
async def async_read_items():
    await asyncio.sleep(1)  # I/O 操作をシミュレートし、1 秒間一時停止
    return {"message": "FastAPI 非同期処理の例"}
```

**Starlette の例**
```python
from starlette.applications import Starlette
from starlette.responses import JSONResponse
import asyncio

# Starlette アプリケーションのインスタンスを作成
app = Starlette()

# アプリケーションのインスタンスに直接ルートを定義し、パスとリクエストメソッドを指定。処理関数は非同期関数
@app.route("/async_items/", methods=["GET"])
async def async_read_items(request):
    await asyncio.sleep(1)  # I/O 操作をシミュレートし、1 秒間一時停止
    return JSONResponse({"message": "Starlette 非同期処理の例"})
```
このように、FastAPI はデコレータを使ってルート定義を簡略化していますが、Starlette は ASGI の本来の操作に近く、アプリケーションのインスタンスに直接ルートを定義し、より柔軟性を提供しています。

### 1.2 ミドルウェアの使用
Starlette は様々な種類のミドルウェアをサポートしています。例えば、単純なロギング ミドルウェアを追加する場合、FastAPI では特定の依存性注入を通じて実装する必要があります：

**Starlette のミドルウェアの例**
```python
from starlette.applications import Starlette
from starlette.responses import JSONResponse
from starlette.middleware.base import BaseHTTPMiddleware
import logging

# ロガーを設定
logger = logging.getLogger(__name__)

# 独自のロギング ミドルウェアで、BaseHTTPMiddleware を継承
class LoggingMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request, call_next):
        # リクエスト情報（リクエストメソッドと URL を含む）をログに記録
        logger.info(f"Request: {request.method} {request.url}")
        # リクエストを続けて処理し、レスポンスを取得
        response = await call_next(request)
        # レスポンスのステータスコードをログに記録
        logger.info(f"Response: {response.status_code}")
        return response

# Starlette アプリケーションのインスタンスを作成し、ミドルウェアのインスタンスを渡す
app = Starlette(middleware=[LoggingMiddleware(app)])

# ルート処理関数を定義
@app.route("/middleware_example/", methods=["GET"])
async def middleware_example(request):
    return JSONResponse({"message": "ミドルウェアが有効です"})
```
FastAPI で同じ機能を実現するには、独自の依存性関数とグローバルな依存性設定に依存する必要があります。比較すると、Starlette のミドルウェアの使用方法はより直感的で、ASGI 仕様の下層に近いものです。

### 1.3 WebSocket のサポート
Starlette はネイティブで WebSocket をサポートしています。以下は、WebSocket チャットの簡単な例です：
```python
from starlette.applications import Starlette
from starlette.websockets import WebSocket, WebSocketDisconnect
import json

# Starlette アプリケーションのインスタンスを作成
app = Starlette()
# 接続されたクライアントの WebSocket オブジェクトを保存
connected_clients = []

# WebSocket ルート処理関数を定義
@app.websocket_route("/ws")
async def websocket_endpoint(websocket: WebSocket):
    await websocket.accept()  # WebSocket 接続を受け入れる
    connected_clients.append(websocket)  # 接続されたクライアントをリストに追加
    try:
        while True:
            # クライアントが送信したテキストデータを受信
            data = await websocket.receive_text()
            message = json.loads(data)  # 受信した JSON 文字列から Python オブジェクトに変換
            for client in connected_clients:
                if client != websocket:
                    # 送信元以外の他のクライアントにメッセージを転送
                    await client.send_text(json.dumps(message))
    except WebSocketDisconnect:
        connected_clients.remove(websocket)  # 接続が切断されたとき、クライアントをリストから削除
```
FastAPI も WebSocket をサポートしていますが、実装の詳細は Starlette と似ています。Starlette は WebSocket 処理のインターフェイスを直接公開しており、開発者がより深くカスタマイズするのに便利です。

## 2. Pydantic の Starlette での応用

### 2.1 データ検証とシリアライズ
Starlette で Pydantic を使ったデータ検証を行う場合、FastAPI と比較します：

**FastAPI の例**
```python
from fastapi import FastAPI
from pydantic import BaseModel

# FastAPI アプリケーションのインスタンスを作成
app = FastAPI()

# Pydantic を使ってデータを検証し、シリアライズするためのデータモデルを定義
class Item(BaseModel):
    name: str
    price: float

# ルート処理関数を定義。FastAPI は自動的に入力データを検証し、レスポンスをシリアライズします
@app.post("/fastapi_items/")
async def create_fastapi_item(item: Item):
    return item
```

**Starlette の例**
```python
from starlette.applications import Starlette
from starlette.responses import JSONResponse
from starlette.requests import Request
from pydantic import BaseModel

# Starlette アプリケーションのインスタンスを作成
app = Starlette()

# Pydantic を使ってデータを検証し、シリアライズするためのデータモデルを定義
class Item(BaseModel):
    name: str
    price: float

# ルート処理関数を定義し、手動でリクエストデータと検証ロジックを処理
@app.route("/starlette_items/", methods=["POST"])
async def create_starlette_item(request: Request):
    data = await request.json()  # リクエストから JSON データを取得
    try:
        item = Item(**data)  # Pydantic を使ってデータを検証。データが有効でない場合は例外が発生する
    except ValueError as e:
        return JSONResponse({"error": str(e)}, status_code=400)  # 検証に失敗した場合、エラーレスポンスを返す
    return JSONResponse(item.dict())  # 検証に成功した場合、シリアライズされたレスポンスを返す
```
FastAPI は自動的にデータ検証とエラー返却を処理しますが、Starlette では開発者が手動で例外をキャッチして処理する必要があります。しかし、このアプローチにより、開発者はより多くの制御権を得ることができます。

### 2.2 複雑なデータモデルとネストされた検証
複雑なデータモデルを扱う場合、Pydantic の利点がより明らかになります：
```python
from starlette.applications import Starlette
from starlette.responses import JSONResponse
from starlette.requests import Request
from pydantic import BaseModel

# Starlette アプリケーションのインスタンスを作成
app = Starlette()

# 住所データモデルを定義
class Address(BaseModel):
    street: str
    city: str
    zip_code: str

# ユーザーデータモデルを定義。住所モデルをネストしています
class User(BaseModel):
    username: str
    email: str
    address: Address

# ルート処理関数を定義し、ユーザーデータの検証と保存を処理
@app.route("/users/", methods=["POST"])
async def create_user(request: Request):
    data = await request.json()  # リクエストから JSON データを取得
    try:
        user = User(**data)  # Pydantic を使ってネストされたデータを検証。データが有効でない場合は例外が発生する
    except ValueError as e:
        return JSONResponse({"error": str(e)}, status_code=400)  # 検証に失敗した場合、エラーレスポンスを返す
    return JSONResponse(user.dict())  # 検証に成功した場合、シリアライズされたレスポンスを返す
```
Starlette でも FastAPI でも、Pydantic はネストされたデータ構造の検証を効率的に処理し、データの完全性と正確性を保証することができます。

## 3. Starlette と Pydantic の深い統合
Starlette のルーティングとミドルウェアと、Pydantic のデータ検証を組み合わせることで、機能の完全な API を構築することができます：
```python
from starlette.applications import Starlette
from starlette.responses import JSONResponse
from starlette.requests import Request
from starlette.exceptions import HTTPException
from starlette.middleware.cors import CORSMiddleware
from pydantic import BaseModel

# Starlette アプリケーションのインスタンスを作成
app = Starlette()
# CORS ミドルウェアを追加し、すべてのオリジンからのリクエストを許可する（本番環境では、特定のドメイン名を制限する必要があります）
app.add_middleware(CORSMiddleware, allow_origins=["*"])

# Pydantic を使って商品データモデルを定義
class Product(BaseModel):
    name: str
    price: float
    quantity: int

# 商品データを保存するリスト
products = []

# 商品を作成するためのルート処理関数を定義
@app.route("/products/", methods=["POST"])
async def create_product(request: Request):
    data = await request.json()  # リクエストから JSON データを取得
    try:
        product = Product(**data)  # Pydantic を使ってデータを検証。データが有効でない場合は例外が発生する
    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))  # 検証に失敗した場合、HTTP 例外を返す
    products.append(product.dict())  # 検証に成功した場合、商品データをリストに追加
    return JSONResponse(product.dict())  # 作成された商品データを返す

# すべての商品を取得するためのルート処理関数を定義
@app.route("/products/", methods=["GET"])
async def get_products(request):
    return JSONResponse(products)  # すべての商品データを返す
```
この例では、Starlette がルーティングとクロスオリジン問題（ミドルウェアを通じて）を処理し、Pydantic がデータ検証とシリアライズを行う完全なプロセスが示されています。FastAPI と比較すると、自動的にドキュメントを生成する機能などは欠けていますが、開発者は実際のニーズに応じて柔軟にサードパーティのライブラリを選択して拡張することができます。例えば、`drf-spectacular` や `apispec` を使って API ドキュメントを生成することができます。

## 結論
Starlette と Pydantic を組み合わせることで、FastAPI のカプセル化に依存することなく、高性能で機能豊富な API を構築することができます。Starlette は柔軟な ASGI アプリケーションの基盤を提供し、非同期処理、ミドルウェア、WebSocket などの核心機能をサポートしています。一方、Pydantic はデータ検証とシリアライズに重点を置いています。FastAPI は開発プロセスを簡略化しますが、Starlette と Pydantic を直接使うことで、開発者は下層の原理をより深く理解することができ、プロジェクトの要件に応じて高度なカスタマイズを行うことができ、複雑なシナリオでより強い適応性を発揮することができます。

# [Leapcell: サーバレス Web ホスティングのベスト](https://leapcell.io/)

最後に、Python サービスのデプロイに最適なプラットフォームをおすすめします：**[Leapcell](https://leapcell.io/)**

[![brandpic7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/e9a00f58-8ddc-4ba6-9014-8de83cd374aa.png)](https://leapcell.io/)


### 🚀 好きな言語で構築
JavaScript、Python、Go、または Rust で簡単に開発できます。  

### 🌍 無料で無制限のプロジェクトをデプロイ
使用した分だけ支払います — リクエストがなければ、請求もありません。  

### ⚡ 使った分だけ支払い、隠れた費用はありません
アイドル料はなく、シームレスにスケーラブルです。  

[![Frame3-withpadding2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/6f9b6502-8668-4a69-b6a0-728ce433a275.png)](https://leapcell.io/)


[📖 ドキュメントを探索する](https://docs.leapcell.io/)  

🔹 Twitter でフォローしてください：[@LeapcellHQ](https://x.com/LeapcellHQ)