Backend

FastAPI入門ガイド - モダンPython Webフレームワーク基本編

kitahara-dev2025/11/0312 min read
FastAPI入門ガイド - モダンPython Webフレームワーク基本編

FastAPI入門ガイド - モダンPython Webフレームワーク基本編

FastAPIは、高速でモダンなPython Webフレームワークです。型ヒントを活用した自動バリデーション、自動ドキュメント生成、高いパフォーマンスが特徴で、REST APIの構築に最適です。このガイドでは、FastAPIの基本を実践的なコード例とともに解説します。

📋 目次

  1. FastAPIとは
  2. セットアップと環境構築
  3. 最初のエンドポイント作成
  4. パスパラメータとクエリパラメータ
  5. リクエストボディとPydanticモデル
  6. レスポンスモデル
  7. HTTPメソッド(GET, POST, PUT, DELETE)
  8. ステータスコード
  9. バリデーション基礎
  10. 自動ドキュメント生成

1. FastAPIとは

FastAPIは、Starlette(高性能ASGIフレームワーク)とPydantic(データバリデーションライブラリ)をベースに構築された、モダンで高速なPython Webフレームワークです。

主な特徴

  • 高速 - NodeJSやGoに匹敵する高いパフォーマンス
  • 型安全 - Python型ヒントによる自動バリデーション
  • 自動ドキュメント - Swagger UI & ReDocが自動生成
  • 開発効率 - 直感的なAPIで高速な開発が可能
  • 非同期対応 - async/awaitをネイティブサポート
  • 標準準拠 - OpenAPI、JSON Schemaに準拠

他フレームワークとの比較

パフォーマンス(リクエスト/秒):
FastAPI:     20,000 - 30,000 req/s
Flask:       2,000 - 5,000 req/s
Django:      1,000 - 3,000 req/s
Node/Express: 15,000 - 25,000 req/s

開発速度:
FastAPI:     ⭐⭐⭐⭐⭐ (型ヒント + 自動ドキュメント)
Flask:       ⭐⭐⭐⭐ (シンプル)
Django:      ⭐⭐⭐ (フル機能だが複雑)

2. セットアップと環境構築

仮想環境の作成

# プロジェクトディレクトリを作成
mkdir fastapi-project
cd fastapi-project

# 仮想環境を作成
python -m venv venv

# 仮想環境を有効化
# Windows
venv\Scripts\activate
# macOS/Linux
source venv/bin/activate

FastAPIのインストール

# FastAPIとUvicorn(ASGIサーバー)をインストール
pip install "fastapi[all]"

# または最小構成
pip install fastapi uvicorn[standard]

# 開発用の依存関係
pip install pytest httpx black flake8

requirements.txt

fastapi==0.104.1
uvicorn[standard]==0.24.0
pydantic==2.5.0
python-multipart==0.0.6  # フォームデータ用

3. 最初のエンドポイント作成

Hello World API

# main.py
from fastapi import FastAPI

app = FastAPI()

@app.get("/")
async def root():
    return {"message": "Hello World"}

@app.get("/hello/{name}")
async def say_hello(name: str):
    return {"message": f"Hello {name}"}

サーバーの起動

# 開発サーバーを起動(ホットリロード有効)
uvicorn main:app --reload

# カスタムポート指定
uvicorn main:app --reload --port 8080

# 外部からアクセス可能にする
uvicorn main:app --host 0.0.0.0 --port 8000

自動ドキュメントにアクセス

ブラウザで以下にアクセス:

  • Swagger UI: http://localhost:8000/docs
  • ReDoc: http://localhost:8000/redoc
  • OpenAPI JSON: http://localhost:8000/openapi.json

4. パスパラメータとクエリパラメータ

パスパラメータ

URLの一部として値を受け取ります。

from fastapi import FastAPI
from enum import Enum

app = FastAPI()

# 基本的なパスパラメータ
@app.get("/items/{item_id}")
async def read_item(item_id: int):
    return {"item_id": item_id}

# 型ヒントによる自動変換とバリデーション
@app.get("/users/{user_id}")
async def read_user(user_id: int):
    # user_idは自動的にintに変換される
    # 変換できない値が来るとエラーレスポンス
    return {"user_id": user_id, "type": type(user_id).__name__}

# Enumを使った制限
class ModelName(str, Enum):
    alexnet = "alexnet"
    resnet = "resnet"
    lenet = "lenet"

@app.get("/models/{model_name}")
async def get_model(model_name: ModelName):
    if model_name == ModelName.alexnet:
        return {"model_name": model_name, "message": "Deep Learning FTW!"}
    if model_name == ModelName.lenet:
        return {"model_name": model_name, "message": "LeCNN all the images"}
    return {"model_name": model_name, "message": "Have some residuals"}

# パスパラメータとして任意のパスを受け取る
@app.get("/files/{file_path:path}")
async def read_file(file_path: str):
    return {"file_path": file_path}
# /files/home/user/document.txt → file_path = "home/user/document.txt"

クエリパラメータ

URLの?以降のkey=value形式のパラメータ。

from fastapi import FastAPI, Query
from typing import Optional

app = FastAPI()

# 基本的なクエリパラメータ
@app.get("/items/")
async def read_items(skip: int = 0, limit: int = 10):
    return {"skip": skip, "limit": limit}
# GET /items/?skip=0&limit=10

# オプショナルなパラメータ
@app.get("/items/{item_id}")
async def read_item(item_id: int, q: Optional[str] = None):
    if q:
        return {"item_id": item_id, "q": q}
    return {"item_id": item_id}

# Queryでバリデーション追加
@app.get("/items/")
async def read_items(
    q: Optional[str] = Query(
        None,
        min_length=3,
        max_length=50,
        regex="^[a-zA-Z0-9]+$",
        description="Search query"
    )
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

# 必須クエリパラメータ
@app.get("/items/")
async def read_items(q: str = Query(..., min_length=3)):
    # ... は必須パラメータを意味する
    return {"q": q}

# 複数の値を受け取る
@app.get("/items/")
async def read_items(q: list[str] = Query([])):
    return {"q": q}
# GET /items/?q=foo&q=bar → {"q": ["foo", "bar"]}

5. リクエストボディとPydanticモデル

Pydanticモデルを使って、リクエストボディのバリデーションと型安全性を実現します。

基本的なPydanticモデル

from fastapi import FastAPI
from pydantic import BaseModel, Field, EmailStr
from typing import Optional
from datetime import datetime

app = FastAPI()

# Pydanticモデルの定義
class User(BaseModel):
    username: str = Field(..., min_length=3, max_length=50)
    email: EmailStr
    full_name: Optional[str] = None
    age: Optional[int] = Field(None, ge=0, le=150)
    is_active: bool = True

@app.post("/users/")
async def create_user(user: User):
    return user

# リクエスト例
# {
#   "username": "johndoe",
#   "email": "john@example.com",
#   "full_name": "John Doe",
#   "age": 30
# }

ネストしたモデル

from pydantic import BaseModel
from typing import List, Optional

class Image(BaseModel):
    url: str
    name: str

class Item(BaseModel):
    name: str
    description: Optional[str] = None
    price: float = Field(..., gt=0, description="価格は0より大きい必要があります")
    tax: Optional[float] = None
    tags: List[str] = []
    images: Optional[List[Image]] = None

@app.post("/items/")
async def create_item(item: Item):
    return item

# リクエスト例
# {
#   "name": "Laptop",
#   "price": 1200.00,
#   "tax": 120.00,
#   "tags": ["electronics", "computers"],
#   "images": [
#     {"url": "http://example.com/img1.jpg", "name": "Front"},
#     {"url": "http://example.com/img2.jpg", "name": "Back"}
#   ]
# }

モデルの設定とバリデーター

from pydantic import BaseModel, Field, validator, field_validator
from datetime import datetime

class BlogPost(BaseModel):
    title: str = Field(..., min_length=5, max_length=100)
    content: str
    published_at: Optional[datetime] = None
    tags: List[str] = []

    # カスタムバリデーター
    @field_validator('title')
    @classmethod
    def title_must_not_contain_spam(cls, v):
        if 'spam' in v.lower():
            raise ValueError('タイトルにspamを含めることはできません')
        return v

    @field_validator('tags')
    @classmethod
    def tags_must_be_unique(cls, v):
        if len(v) != len(set(v)):
            raise ValueError('タグは重複できません')
        return v

    # モデル設定
    class Config:
        json_schema_extra = {
            "example": {
                "title": "FastAPI入門",
                "content": "FastAPIの基本的な使い方...",
                "published_at": "2025-11-03T10:00:00",
                "tags": ["python", "fastapi", "tutorial"]
            }
        }

@app.post("/posts/")
async def create_post(post: BlogPost):
    return post

6. レスポンスモデル

レスポンスの形式を定義し、自動的にシリアライズします。

from pydantic import BaseModel, EmailStr
from typing import Optional

# レスポンスモデル(パスワードフィールドを除外)
class UserIn(BaseModel):
    username: str
    password: str
    email: EmailStr
    full_name: Optional[str] = None

class UserOut(BaseModel):
    username: str
    email: EmailStr
    full_name: Optional[str] = None

@app.post("/users/", response_model=UserOut)
async def create_user(user: UserIn):
    # パスワードは自動的にレスポンスから除外される
    return user

# レスポンス例
# {
#   "username": "johndoe",
#   "email": "john@example.com",
#   "full_name": "John Doe"
# }
# ※ password フィールドは含まれない

レスポンスモデルのオプション

from typing import List

class Item(BaseModel):
    name: str
    description: Optional[str] = None
    price: float
    tax: Optional[float] = None

# リストで返す
@app.get("/items/", response_model=List[Item])
async def read_items():
    return [
        {"name": "Item 1", "price": 10.5},
        {"name": "Item 2", "price": 20.0, "tax": 2.0}
    ]

# Noneを除外してレスポンス
@app.get("/items/{item_id}", response_model=Item, response_model_exclude_unset=True)
async def read_item(item_id: int):
    return {"name": "Item", "price": 10.5}
    # description と tax は設定されていないので除外される

# 特定のフィールドを除外
@app.get("/items/{item_id}", response_model=Item, response_model_exclude={"tax"})
async def read_item(item_id: int):
    return {"name": "Item", "price": 10.5, "tax": 1.0}
    # tax フィールドは除外される

7. HTTPメソッド(GET, POST, PUT, DELETE)

CRUD操作の実装

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import List, Dict

app = FastAPI()

# データストア(実際はDBを使用)
items_db: Dict[int, dict] = {}
item_id_counter = 1

class Item(BaseModel):
    name: str
    description: Optional[str] = None
    price: float
    in_stock: bool = True

class ItemUpdate(BaseModel):
    name: Optional[str] = None
    description: Optional[str] = None
    price: Optional[float] = None
    in_stock: Optional[bool] = None

# CREATE
@app.post("/items/", response_model=Item, status_code=201)
async def create_item(item: Item):
    global item_id_counter
    item_id = item_id_counter
    items_db[item_id] = item.model_dump()
    items_db[item_id]["id"] = item_id
    item_id_counter += 1
    return items_db[item_id]

# READ - すべて取得
@app.get("/items/", response_model=List[Item])
async def read_items(skip: int = 0, limit: int = 10):
    items = list(items_db.values())
    return items[skip : skip + limit]

# READ - 1件取得
@app.get("/items/{item_id}", response_model=Item)
async def read_item(item_id: int):
    if item_id not in items_db:
        raise HTTPException(status_code=404, detail="Item not found")
    return items_db[item_id]

# UPDATE - 完全更新
@app.put("/items/{item_id}", response_model=Item)
async def update_item(item_id: int, item: Item):
    if item_id not in items_db:
        raise HTTPException(status_code=404, detail="Item not found")
    items_db[item_id] = item.model_dump()
    items_db[item_id]["id"] = item_id
    return items_db[item_id]

# UPDATE - 部分更新
@app.patch("/items/{item_id}", response_model=Item)
async def partial_update_item(item_id: int, item: ItemUpdate):
    if item_id not in items_db:
        raise HTTPException(status_code=404, detail="Item not found")

    stored_item = items_db[item_id]
    update_data = item.model_dump(exclude_unset=True)
    stored_item.update(update_data)
    return stored_item

# DELETE
@app.delete("/items/{item_id}", status_code=204)
async def delete_item(item_id: int):
    if item_id not in items_db:
        raise HTTPException(status_code=404, detail="Item not found")
    del items_db[item_id]
    return None  # 204 No Content

8. ステータスコード

HTTPステータスコードを適切に使用します。

from fastapi import FastAPI, status, HTTPException

app = FastAPI()

# 明示的にステータスコードを指定
@app.post("/items/", status_code=status.HTTP_201_CREATED)
async def create_item(item: Item):
    return item

# 成功レスポンス
@app.get("/items/", status_code=status.HTTP_200_OK)
async def read_items():
    return [{"name": "Item 1"}]

# 作成成功
@app.post("/users/", status_code=status.HTTP_201_CREATED)
async def create_user(user: User):
    return user

# コンテンツなし
@app.delete("/items/{item_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_item(item_id: int):
    return None

# エラーレスポンス
@app.get("/items/{item_id}")
async def read_item(item_id: int):
    if item_id not in items_db:
        # 404 Not Found
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Item not found"
        )
    return items_db[item_id]

# カスタムエラーレスポンス
@app.post("/users/")
async def create_user(user: User):
    if user.username in users_db:
        # 409 Conflict
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail="Username already exists",
            headers={"X-Error": "Username conflict"}
        )
    return user

# 主要なステータスコード
# 200 OK - 成功
# 201 Created - 作成成功
# 204 No Content - 成功(コンテンツなし)
# 400 Bad Request - リクエストエラー
# 401 Unauthorized - 認証エラー
# 403 Forbidden - 権限エラー
# 404 Not Found - 見つからない
# 409 Conflict - 競合
# 422 Unprocessable Entity - バリデーションエラー(FastAPIのデフォルト)
# 500 Internal Server Error - サーバーエラー

9. バリデーション基礎

Pydanticによる強力なバリデーション機能。

from pydantic import BaseModel, Field, validator, EmailStr, HttpUrl
from typing import Optional, List
from datetime import datetime

class Product(BaseModel):
    # 基本的なバリデーション
    name: str = Field(..., min_length=1, max_length=100)
    sku: str = Field(..., regex=r'^[A-Z]{3}-[0-9]{4}$')
    price: float = Field(..., gt=0, le=1000000)
    discount: Optional[float] = Field(None, ge=0, le=100)

    # 型による自動バリデーション
    email: EmailStr
    website: HttpUrl
    tags: List[str] = Field(default_factory=list, max_items=10)
    created_at: datetime = Field(default_factory=datetime.now)

    # カスタムバリデーター
    @field_validator('discount')
    @classmethod
    def validate_discount(cls, v, values):
        if v and v > 50:
            raise ValueError('割引は50%以下にしてください')
        return v

    @field_validator('tags')
    @classmethod
    def validate_tags(cls, v):
        if len(v) != len(set(v)):
            raise ValueError('タグに重複があります')
        return [tag.lower() for tag in v]

@app.post("/products/")
async def create_product(product: Product):
    return product

# バリデーションエラーのレスポンス例
# {
#   "detail": [
#     {
#       "loc": ["body", "price"],
#       "msg": "ensure this value is greater than 0",
#       "type": "value_error.number.not_gt"
#     }
#   ]
# }

クエリパラメータのバリデーション

from fastapi import Query

@app.get("/search/")
async def search_items(
    q: str = Query(
        ...,
        min_length=3,
        max_length=50,
        regex=r'^[a-zA-Z0-9s]+$',
        title="検索クエリ",
        description="検索するキーワード(3-50文字、英数字とスペースのみ)"
    ),
    page: int = Query(1, ge=1, le=100, description="ページ番号"),
    size: int = Query(10, ge=1, le=100, description="1ページあたりの件数")
):
    return {
        "q": q,
        "page": page,
        "size": size,
        "offset": (page - 1) * size
    }

10. 自動ドキュメント生成

FastAPIの最大の魅力の一つは、自動生成されるインタラクティブなAPIドキュメントです。

メタ情報の設定

from fastapi import FastAPI

app = FastAPI(
    title="My Awesome API",
    description="""
    # FastAPI入門ガイド

    このAPIは以下の機能を提供します:

    ## Items
    アイテムの**CRUD操作**を実行できます。

    ## Users
    ユーザー管理機能を提供します。

    ## 特徴
    * 自動バリデーション
    * 型安全
    * 高速なレスポンス
    """,
    version="1.0.0",
    terms_of_service="http://example.com/terms/",
    contact={
        "name": "API Support",
        "url": "http://example.com/contact/",
        "email": "support@example.com",
    },
    license_info={
        "name": "MIT License",
        "url": "https://opensource.org/licenses/MIT",
    },
)

エンドポイントのドキュメント

from fastapi import FastAPI, Path, Query, Body

@app.post(
    "/items/",
    response_model=Item,
    status_code=201,
    summary="アイテムを作成",
    description="新しいアイテムをデータベースに作成します。",
    response_description="作成されたアイテム",
    tags=["items"],
)
async def create_item(
    item: Item = Body(
        ...,
        example={
            "name": "Laptop",
            "description": "高性能ノートPC",
            "price": 1200.00,
            "in_stock": True
        }
    )
):
    """
    アイテムを作成します:

    - **name**: アイテム名(必須)
    - **description**: 説明(オプション)
    - **price**: 価格(必須、0より大きい値)
    - **in_stock**: 在庫状況(デフォルト: true)
    """
    return item

タグによるグループ化

tags_metadata = [
    {
        "name": "items",
        "description": "アイテムの管理操作",
    },
    {
        "name": "users",
        "description": "ユーザー管理操作",
        "externalDocs": {
            "description": "ユーザー管理の詳細ドキュメント",
            "url": "https://example.com/docs/users",
        },
    },
]

app = FastAPI(openapi_tags=tags_metadata)

@app.get("/items/", tags=["items"])
async def read_items():
    return [{"name": "Item 1"}]

@app.get("/users/", tags=["users"])
async def read_users():
    return [{"username": "user1"}]

📝 まとめ

FastAPI基本編で学んだ内容:

基礎知識

  • FastAPIの特徴 - 高速、型安全、自動ドキュメント
  • セットアップ - 仮想環境、インストール、起動
  • エンドポイント作成 - ルーティング、デコレーター

データ処理

  • パスパラメータ - URLからの値取得、型変換
  • クエリパラメータ - オプショナル、必須、バリデーション
  • リクエストボディ - Pydanticモデル、ネストしたデータ

レスポンス

  • レスポンスモデル - 型安全なレスポンス、フィールド除外
  • HTTPメソッド - GET, POST, PUT, PATCH, DELETE
  • ステータスコード - 適切なコード選択

品質保証

  • バリデーション - 自動バリデーション、カスタムルール
  • ドキュメント - Swagger UI、ReDoc、メタ情報

🔗 次のステップ

基本をマスターしたら、次の記事で以下を学習しましょう:

  • FastAPI中級編 - 認証・認可、ミドルウェア、CORS
  • FastAPI実践編 - データベース連携、テスト、デプロイ

🔗 参考リンク

FastAPIで高速で型安全なWeb APIを構築しましょう!

#Python#FastAPI#API#Backend#Pydantic

著者について

kitahara-devによって執筆されました。