はじめに
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でdataclassやPydanticのモデルを手書きするのに比べて、モデルと連動するので重複が少ない。
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を選ぶのが正解だと思えてきた。