1
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?

Django REST Framework — APIを作ってFastAPIと比べた

1
Posted at

はじめに

Djangoの基本を触ったので、次はDjango REST Framework(DRF)。

DjangoでAPIを作るときのデファクトスタンダードがDRF。FastAPIをある程度書いてきたので、「同じPythonのAPIフレームワークでここまで思想が違うのか」という部分が面白かった。特にSerializerの概念がFastAPIのPydanticとは別の設計になっていて、整理するのに時間がかかった。


インストール

pip install djangorestframework
# settings.py
INSTALLED_APPS = [
    ...
    "rest_framework",  # 追加
]

# DRFのデフォルト設定
REST_FRAMEWORK = {
    "DEFAULT_RENDERER_CLASSES": [
        "rest_framework.renderers.JSONRenderer",
    ],
    "DEFAULT_PARSER_CLASSES": [
        "rest_framework.parsers.JSONParser",
    ],
}

Serializer — DRFの中心概念

DRFを理解する上で一番重要なのがSerializer。

FastAPIのPydanticモデルと役割が似ているが、バリデーション・シリアライズ・デシリアライズを1つのクラスで担う設計になっている。

# users/serializers.py
from rest_framework import serializers
from .models        import User

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model  = User
        fields = ["id", "name", "email", "age", "created_at"]
        # または fields = "__all__" で全フィールド
        read_only_fields = ["id", "created_at"]

ModelSerializerはモデルの定義からフィールドを自動生成してくれる。FastAPIでdataclassPydanticのモデルを手書きするのに比べて、モデルと連動するので重複が少ない。

Serializerの3つの役割

from users.serializers import UserSerializer
from users.models      import User

# ① シリアライズ(モデル → JSON用辞書)
user       = User.objects.get(id=1)
serializer = UserSerializer(user)
print(serializer.data)
# {'id': 1, 'name': '田中', 'email': 'tanaka@example.com', ...}

# ② 複数オブジェクトのシリアライズ
users      = User.objects.all()
serializer = UserSerializer(users, many=True)
print(serializer.data)
# [{'id': 1, ...}, {'id': 2, ...}]

# ③ デシリアライズ(受信データ → バリデーション → 保存)
data = {"name": "鈴木", "email": "suzuki@example.com"}
serializer = UserSerializer(data=data)
if serializer.is_valid():
    user = serializer.save()  # DBに保存
else:
    print(serializer.errors)  # バリデーションエラー

FastAPIはPydanticモデルとORMモデルを別々に定義するが、DRFのSerializerはその橋渡し役も担う。


カスタムバリデーション

from rest_framework import serializers
from .models        import User

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model  = User
        fields = ["id", "name", "email", "password", "password_confirmation"]
        extra_kwargs = {
            "password": {"write_only": True},  # レスポンスに含めない
        }

    # フィールド単位のバリデーション
    def validate_name(self, value: str) -> str:
        if len(value.strip()) == 0:
            raise serializers.ValidationError("名前は空白のみでは登録できません")
        return value.strip()

    # 複数フィールドをまたぐバリデーション
    def validate(self, data: dict) -> dict:
        if data["password"] != data["password_confirmation"]:
            raise serializers.ValidationError("パスワードが一致しません")
        return data

    # 保存処理のカスタマイズ
    def create(self, validated_data: dict):
        validated_data.pop("password_confirmation")
        return User.objects.create(**validated_data)

FastAPIの@field_validator@model_validatorに相当するのがvalidate_<フィールド名>validateメソッド。命名規則でバリデーターが呼ばれる仕組みはDjangoらしい設計。


APIView — ビューの基本

# users/views.py
from rest_framework.views    import APIView
from rest_framework.response import Response
from rest_framework          import status
from .models                 import User
from .serializers            import UserSerializer

class UserListView(APIView):
    def get(self, request):
        users      = User.objects.all()
        serializer = UserSerializer(users, many=True)
        return Response(serializer.data)

    def post(self, request):
        serializer = UserSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)


class UserDetailView(APIView):
    def get_object(self, pk: int):
        try:
            return User.objects.get(pk=pk)
        except User.DoesNotExist:
            return None

    def get(self, request, pk: int):
        user = self.get_object(pk)
        if user is None:
            return Response({"error": "見つかりません"}, status=status.HTTP_404_NOT_FOUND)
        serializer = UserSerializer(user)
        return Response(serializer.data)

    def put(self, request, pk: int):
        user = self.get_object(pk)
        if user is None:
            return Response({"error": "見つかりません"}, status=status.HTTP_404_NOT_FOUND)
        serializer = UserSerializer(user, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    def delete(self, request, pk: int):
        user = self.get_object(pk)
        if user is None:
            return Response({"error": "見つかりません"}, status=status.HTTP_404_NOT_FOUND)
        user.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

FastAPIと比べるとコード量が多い。serializer.is_valid()serializer.save()の2ステップを毎回書く必要がある。


ViewSet — CRUDをまとめて書く

毎回get/post/put/deleteを書くのは冗長なので、ViewSetを使うとまとめられる。

from rest_framework       import viewsets
from rest_framework.response import Response
from .models              import User
from .serializers         import UserSerializer

class UserViewSet(viewsets.ModelViewSet):
    queryset           = User.objects.all()
    serializer_class   = UserSerializer

これだけでCRUD(一覧・詳細・作成・更新・削除)が全部動く。ModelViewSetを継承するだけでLaravelのリソースコントローラーと同じ機能が手に入る。

ViewSetのカスタマイズ

from rest_framework       import viewsets, status
from rest_framework.decorators import action
from rest_framework.response   import Response

class UserViewSet(viewsets.ModelViewSet):
    queryset         = User.objects.all()
    serializer_class = UserSerializer

    # クエリセットのフィルタリング
    def get_queryset(self):
        queryset = User.objects.all()
        is_active = self.request.query_params.get("is_active")
        if is_active is not None:
            queryset = queryset.filter(is_active=is_active == "true")
        return queryset

    # カスタムアクション(標準のCRUD以外の処理)
    @action(detail=True, methods=["post"], url_path="activate")
    def activate(self, request, pk=None):
        user = self.get_object()
        user.is_active = True
        user.save()
        return Response({"status": "アクティブにしました"})

@actionデコレータでCRUD以外のエンドポイントを追加できる。detail=TrueでIDが必要なエンドポイント(/users/{id}/activate/)、detail=Falseでコレクション操作(/users/bulk_delete/)になる。


Router — URLを自動生成する

ViewSetと組み合わせてURLを自動生成できる。

# users/urls.py
from rest_framework.routers import DefaultRouter
from .views import UserViewSet

router = DefaultRouter()
router.register(r"users", UserViewSet, basename="user")

urlpatterns = router.urls

これだけで以下のURLが自動生成される。

GET    /users/          → 一覧
POST   /users/          → 作成
GET    /users/{id}/     → 詳細
PUT    /users/{id}/     → 全体更新
PATCH  /users/{id}/     → 部分更新
DELETE /users/{id}/     → 削除
POST   /users/{id}/activate/  → カスタムアクション

LaravelのRoute::apiResource()に相当する。FastAPIでは自前でエンドポイントを全部書くのに比べると、DRFのRouter + ViewSetは定型CRUDを最小コードで実装できる。


ネストしたSerializer

class AddressSerializer(serializers.ModelSerializer):
    class Meta:
        model  = Address
        fields = ["postal_code", "prefecture", "city"]

class UserSerializer(serializers.ModelSerializer):
    address = AddressSerializer()  # ネスト

    class Meta:
        model  = User
        fields = ["id", "name", "email", "address"]
# レスポンス
{
    "id": 1,
    "name": "田中",
    "email": "tanaka@example.com",
    "address": {
        "postal_code": "100-0001",
        "prefecture": "東京都",
        "city": "千代田区"
    }
}

FastAPIのネストしたPydanticモデルと同じ感覚で書ける。


認証と権限

# settings.py
REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": [
        "rest_framework.authentication.SessionAuthentication",
        "rest_framework.authentication.BasicAuthentication",
    ],
    "DEFAULT_PERMISSION_CLASSES": [
        "rest_framework.permissions.IsAuthenticated",  # デフォルトで認証必須
    ],
}
from rest_framework.permissions import IsAuthenticated, AllowAny, IsAdminUser

class UserViewSet(viewsets.ModelViewSet):
    queryset         = User.objects.all()
    serializer_class = UserSerializer

    # ビュー単位で権限を上書き
    def get_permissions(self):
        if self.action == "list":
            return [AllowAny()]
        return [IsAuthenticated()]

JWTを使う場合はdjangorestframework-simplejwtを追加する。

pip install djangorestframework-simplejwt
# urls.py
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView

urlpatterns = [
    path("auth/token/",         TokenObtainPairView.as_view()),
    path("auth/token/refresh/", TokenRefreshView.as_view()),
]

FastAPIのDepends()で認証関数を注入するのと比べると、DRFは設定ファイルでデフォルトを決めてビュー単位で上書きする設計。設定が一箇所にまとまるのはDRFのほうが管理しやすい。


ページネーション

# settings.py
REST_FRAMEWORK = {
    "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
    "PAGE_SIZE": 20,
}

これだけで全ViewSetにページネーションが適用される。

GET /users/?page=2

{
    "count":    100,
    "next":     "http://localhost:8000/users/?page=3",
    "previous": "http://localhost:8000/users/?page=1",
    "results":  [...]
}

FastAPIはページネーションを自前で実装するのが基本なので、ここはDRFが楽。


フィルタリング

pip install django-filter
# settings.py
INSTALLED_APPS = [..., "django_filters"]
REST_FRAMEWORK = {
    "DEFAULT_FILTER_BACKENDS": ["django_filters.rest_framework.DjangoFilterBackend"],
}
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework                import filters

class UserViewSet(viewsets.ModelViewSet):
    queryset         = User.objects.all()
    serializer_class = UserSerializer
    filter_backends  = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
    filterset_fields = ["is_active", "age"]     # 完全一致フィルタ
    search_fields    = ["name", "email"]         # 部分一致検索
    ordering_fields  = ["created_at", "name"]    # ソート
GET /users/?is_active=true&search=田中&ordering=-created_at

クエリパラメータでのフィルタ・検索・ソートが設定だけで動く。FastAPIでは全部手書きになる部分。


FastAPIとDRFの比較

同じCRUD APIを両方で書いたときの対比。

# FastAPI版
from fastapi  import FastAPI, HTTPException, Depends
from pydantic import BaseModel

app = FastAPI()

class UserCreate(BaseModel):
    name:  str
    email: str

class UserResponse(BaseModel):
    id:    int
    name:  str
    email: str

@app.get("/users", response_model=list[UserResponse])
def list_users(db: Session = Depends(get_db)):
    return db.query(User).all()

@app.post("/users", response_model=UserResponse, status_code=201)
def create_user(user: UserCreate, db: Session = Depends(get_db)):
    db_user = User(**user.model_dump())
    db.add(db_user)
    db.commit()
    return db_user
# DRF版
class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model  = User
        fields = ["id", "name", "email"]

class UserViewSet(viewsets.ModelViewSet):
    queryset         = User.objects.all()
    serializer_class = UserSerializer

CRUDの定型処理はDRFのほうが圧倒的に少ないコードで書ける。FastAPIはその分自由度が高いが、毎回自前で書く量が多い。


DRFとFastAPIの使い分けまとめ

項目 DRF FastAPI
定型CRUD ViewSet + Routerで最小コード 全部自前で書く
バリデーション Serializer Pydantic
ドキュメント 別途設定 自動生成(/docs)
ページネーション 設定一行 自前実装
フィルタ・ソート django-filterで設定のみ 自前実装
認証 設定ファイルで一括 Dependsで関数注入
非同期 限定的 ネイティブ
型安全 弱い(Serializerは型ヒント薄い) 強い(Pydantic中心)
Admin画面 Django Adminと統合 なし

DRFはDjangoのエコシステム(Admin・ORM・認証)と統合されているので、フルスタックなWebシステムを作るなら強い。FastAPIは型安全と非同期が必要なAPIサーバーに向いている。


まとめ

  • SerializerがDRFの中心概念。バリデーション・シリアライズ・DBへの保存を一括で担う
  • ViewSet + Routerで定型CRUDが最小コードで書ける
  • ページネーション・フィルタリング・認証が設定だけで動く
  • FastAPIと比べるとコードの柔軟性は下がるが、定型処理のボイラープレートが少ない

FastAPIを先に覚えていたのでDRFの「設定で動かす」設計が最初は窮屈に感じたが、管理画面込みのシステムを作るなら最初からDjangoを選ぶのが正解だと思えてきた。

1
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
1
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?