1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

DjangoRestFrameworkでAPI構築してみよう

1
Last updated at Posted at 2026-03-09

Qiita技術ブログ (36).png

はじめに

Django REST Framework(以下DRF)は、Django上でREST APIを構築するためのライブラリです。
DjangoのORM・認証機能・Adminと組み合わせることで、短時間で堅牢なAPIを作ることができます。

DRFの基本構成である、Model/Serializer/View/Routerを一通り実装するサンプルとしてシンプルな読書管理APIを実装します。

ER図

このサンプルは以下の3テーブルを作成します。

  • users: ログインユーザー
  • books: 書籍マスタ
  • user_books: ユーザーごとの読書ステータス

user_booksはユーザーと書籍の中間テーブルとして、読書ステータスやメモなどを管理できるように設計しています。

db_er.png

API完成イメージ

  • 認証API
    • POST /api/v1/auth/register/
    • POST /api/v1/auth/login/
    • POST /api/v1/auth/logout/
  • 書籍API(認証不要でAPIからは参照のみ行う)
    • GET /api/v1/books/
    • GET /api/v1/books/{id}/
  • ユーザーごとの読書状態管理用API(ログインユーザーのみ操作)
    • GET/POST /api/v1/user-books/
    • GET/PUT/PATCH/DELETE /api/v1/user-books/{id}/

API定義.png

作成したコード

プロジェクト環境作成

一番最初に以下のようなディレクトリ構成になるようにします

qiita-drf(プロジェクト名は適宜変更お願いします。)
├── requirements.txt
└── venv

作成コマンド例。

mkdir qiita-drf
cd qiita-drf
python -m venv venv
# 仮想環境の有効化(Linux/Mac)
source ./venv/bin/activate
# Windowsの方は以下を実行
# .\venv\Scripts\activate

今回は仮想環境としてvenvを使用しています。

requirements.txtの内容は以下となっています。

requirements.txt
django==5.2.12
djangorestframework==3.16.1
markdown==3.10.2
django-cors-headers==4.9.0

使用するライブラリインストール

requirements.txtのインストールを行います。

python -m pip install --upgrade pip
pip install -r requirements.txt

Djangoプロジェクト作成

プロジェクト作成

以下コマンドでdjangoプロジェクトの作成を行います。
sample_configは適宜自分のプロジェクト名に変えてください。

django-admin startproject sample_config .

アプリ作成

以下コマンドでdjangoアプリの作成を行います。
sample_appは適宜自分のアプリ名に変えてください。

./manage.py startapp sample_app

プロジェクト作成後のディレクトリ構成は以下のようになります

qiita-drf
├── manage.py
├── requirements.txt
├── venv
│   └── ...省略
├── sample_app
│   ├── __init__.py
│   ├── admin.py
│   ├── apps.py
│   ├── migrations
│   │   └── __init__.py
│   ├── models.py
│   ├── tests.py
│   └── views.py
└── sample_config
    ├── __init__.py
    ├── asgi.py
    ├── settings.py
    ├── urls.py
    └── wsgi.py

settings.py設定

settings.py
+ if DEBUG:  # 今回のサンプルコードでは動作確認のためデバック時のみCORSを全許可してます
+     CORS_ALLOW_ALL_ORIGINS = True

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    ...
+   'rest_framework',
+   'corsheaders',
+   # 自作APPS
+   'sample_app'
]

+ REST_FRAMEWORK = {
+     "DEFAULT_AUTHENTICATION_CLASSES": [
+         "rest_framework.authentication.SessionAuthentication",
+     ],
+     "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.LimitOffsetPagination",
+     "PAGE_SIZE": 10
+ }

MIDDLEWARE = [
+   'corsheaders.middleware.CorsMiddleware',
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

日本語設定 / タイムゾーン設定

settings.py
- LANGUAGE_CODE = 'en-us'
+ LANGUAGE_CODE = 'ja'

- TIME_ZONE = 'UTC'
+ TIME_ZONE = 'Asia/Tokyo'

modelの定義

今回のサンプルではmodels/にmodelの定義を集約しています。

sample_app/models/__init__.py
from .user import UserModel
from .book import BookModel
from .user_book import UserBookModel

__all__ = ["UserModel", "BookModel", "UserBookModel"]

Bookモデル

書籍情報を管理します。

sample_app/models/book.py
from django.db import models


class BookModel(models.Model):
    """書籍を表すモデル。"""

    title = models.CharField("タイトル", max_length=255)
    author = models.CharField("著者", max_length=255)
    created_at = models.DateTimeField("作成日時", auto_now_add=True)
    updated_at = models.DateTimeField("更新日時", auto_now=True)

    class Meta:
        db_table = "books"
        verbose_name = "書籍"
        verbose_name_plural = "書籍一覧"
        ordering = ["-created_at"]

    def __str__(self) -> str:
        """管理画面向けの表示名"""
        return f"{self.title} / {self.author}"

UserBookモデル

ユーザーごとの読書状態を管理します。

sample_app/models/user_book.py
from django.conf import settings
from django.db import models


class ReadingStatus(models.TextChoices):
    """読書ステータス定義"""

    UNREAD = "unread", "未読"
    READING = "reading", "読書中"
    FINISHED = "finished", "読了"


class UserBookModel(models.Model):
    """ユーザーと書籍の中間モデル"""

    user = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        verbose_name="ユーザー",
        on_delete=models.CASCADE,
        related_name="user_books",
    )
    book = models.ForeignKey(
        "sample_app.BookModel",
        verbose_name="書籍",
        on_delete=models.CASCADE,
        related_name="user_books",
    )
    status = models.CharField("読書ステータス", max_length=20, choices=ReadingStatus.choices, default=ReadingStatus.UNREAD)
    memo = models.TextField("メモ", blank=True, default="")
    created_at = models.DateTimeField("作成日時", auto_now_add=True)
    updated_at = models.DateTimeField("更新日時", auto_now=True)

    class Meta:
        db_table = "user_books"
        verbose_name = "ユーザー書籍"
        verbose_name_plural = "ユーザー書籍一覧"
        constraints = [
            models.UniqueConstraint(fields=["user", "book"], name="unique_user_book"),
        ]

    def __str__(self) -> str:
        """管理画面向けの表示名"""
        return f"{self.user} - {self.book} ({self.status})"

ユーザーモデル

今回はemail/password認証を採用しています。

sample_app/models/user.py
from __future__ import annotations

from django.contrib.auth.base_user import BaseUserManager
from django.contrib.auth.models import AbstractUser
from django.db import models


class UserManager(BaseUserManager["UserModel"]):
    """メールアドレス認証用のユーザーマネージャ。"""

    use_in_migrations = True

    def create_user(self, email: str, password: str | None = None, **extra_fields: object) -> "UserModel":
        """通常ユーザーを作成する

        Args:
            email: ログインに利用するメールアドレス
            password: 平文パスワード
            **extra_fields: 追加のユーザー属性

        Returns:
            作成されたユーザー
        """
        if not email:
            raise ValueError("メールアドレスは必須です。")

        normalized_email = self.normalize_email(email)
        user = self.model(email=normalized_email, **extra_fields)
        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_superuser(self, email: str, password: str, **extra_fields: object) -> "UserModel":
        """スーパーユーザーを作成する

        Args:
            email: ログインに利用するメールアドレス
            password: 平文パスワード
            **extra_fields: 追加のユーザー属性

        Returns:
            作成されたユーザー
        """
        extra_fields.setdefault("is_staff", True)
        extra_fields.setdefault("is_superuser", True)
        extra_fields.setdefault("is_active", True)

        return self.create_user(email=email, password=password, **extra_fields)


class UserModel(AbstractUser):
    """email/passwordで認証を行うカスタムユーザーモデル"""

    # 今回のサンプルプロジェクトはusername, first_name, last_nameは不要なカラムのためNoneにする
    username = None 
    first_name = None
    last_name = None

    email = models.EmailField("メールアドレス", unique=True)

    USERNAME_FIELD = "email"
    REQUIRED_FIELDS: list[str] = []

    objects = UserManager()

    class Meta:
        db_table = "users"
        verbose_name = "ユーザー"
        verbose_name_plural = "ユーザー一覧"


    def __str__(self) -> str:
        return self.email

settings.pyにUser設定

settings.py
+ AUTH_USER_MODEL = "sample_app.UserModel"

modelをdbに反映

./manage.py makemigrations
./manage.py migrate

管理画面ログイン用ユーザー作成

以下コマンドで開発用の管理画面ログイン用ユーザー作成を行います。
メールアドレス・パスワードは適宜入力お願いします。

./manage.py createsuperuser

サーバー立ち上げ

runserverをすると管理画面が表示されるため、先ほど作成した管理画面ログイン用ユーザーのログイン情報でログインを行います。

./manage.py runserver

ログイン画面
管理画面ログイン画面.png

ログイン後の管理画面

admin定義前の管理画面.png

まだadmin.pyの定義をしていないため、作成したテーブルが表示されていません。
次にadmin.pyに管理画面へ表示するmodelの設定を定義します。

admin.pyの定義

sample_app/admin.py
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin

from .models import BookModel, UserBookModel, UserModel


@admin.register(UserModel)
class UserModelAdmin(UserAdmin):
    ordering = ("id",)
    list_display = ("id", "email", "is_staff", "is_active", "last_login", "date_joined")
    search_fields = ("email",)
    list_filter = ("is_staff", "is_superuser", "is_active")

    fieldsets = (
        (None, {"fields": ("email", "password")}),
        ("権限", {"fields": ("is_active", "is_staff", "is_superuser", "groups", "user_permissions")}),
        ("最終ログイン・登録日時", {"fields": ("last_login", "date_joined")}),
    )
    add_fieldsets = (
        (
            None,
            {
                "classes": ("wide",),
                "fields": ("email", "password1", "password2", "is_staff", "is_superuser", "is_active"),
            },
        ),
    )


@admin.register(BookModel)
class BookModelAdmin(admin.ModelAdmin):
    list_display = ("id", "title", "author", "created_at", "updated_at")
    search_fields = ("title", "author")
    ordering = ("-created_at",)
    list_per_page = 30


@admin.register(UserBookModel)
class UserBookModelAdmin(admin.ModelAdmin):
    list_display = ("id", "user", "book", "status", "updated_at")
    search_fields = ("user__email", "book__title", "book__author", "memo")
    list_filter = ("status", "created_at", "updated_at")
    autocomplete_fields = ("user", "book")
    ordering = ("-updated_at",)
    list_select_related = ("user", "book")
    list_per_page = 50

admin.py定義後に管理画面を確認すると作成したモデルが表示されていることが確認できます。

admin定義後の管理画面.png

API実装

では今回のメインであるDRFのSerializer/View/Routerの実装に入ります。

Serializezr実装

DRFではSerializerを使用してデータの変換とバリデーションを行います。
今回はModelSerializerでサンプル実装してみました。

認証用Serializer

sample_app/serializers/auth.py
from django.contrib.auth import get_user_model
from rest_framework import serializers


UserModel = get_user_model()


class RegisterSerializer(serializers.ModelSerializer):
    password = serializers.CharField(write_only=True, min_length=8)

    class Meta:
        model = UserModel
        fields = ["id", "email", "password"]
        read_only_fields = ["id"]

    def create(self, validated_data):
        password = validated_data.pop("password")
        return UserModel.objects.create_user(password=password, **validated_data)


class LoginSerializer(serializers.Serializer):
    email = serializers.EmailField()
    password = serializers.CharField(write_only=True)

書籍情報用Serializer

sample_app/serializers/book.py
from rest_framework import serializers

from sample_app.models import BookModel


class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = BookModel
        fields = ["id", "title", "author", "created_at", "updated_at"]
        read_only_fields = ["id", "created_at", "updated_at"]

ユーザーごとの読書状態管理用Serializer

sample_app/serializers/user_book.py
from rest_framework import serializers

from sample_app.models import UserBookModel


class UserBookSerializer(serializers.ModelSerializer):
    class Meta:
        model = UserBookModel
        fields = [
            "id",
            "user",
            "book",
            "status",
            "memo",
            "created_at",
            "updated_at",
        ]
        read_only_fields = ["id", "user", "created_at", "updated_at"]

views実装

認証用view

sample_app/views/auth.py
from django.contrib.auth import authenticate, get_user_model, login, logout
from rest_framework import permissions, status
from rest_framework.response import Response
from rest_framework.views import APIView

from sample_app.serializers.auth import LoginSerializer, RegisterSerializer


UserModel = get_user_model()


class RegisterAPIView(APIView):
    permission_classes = [permissions.AllowAny]

    def post(self, request):
        serializer = RegisterSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        user = serializer.save()

        response_data = {
            "id": user.id,
            "email": user.email,
        }
        return Response(response_data, status=status.HTTP_201_CREATED)


class LoginAPIView(APIView):
    permission_classes = [permissions.AllowAny]

    def post(self, request):
        serializer = LoginSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)

        email = serializer.validated_data["email"]
        password = serializer.validated_data["password"]

        user = authenticate(request, email=email, password=password)
        if user is None:
            return Response({"detail": "メールアドレスまたはパスワードが正しくありません。"}, status=status.HTTP_401_UNAUTHORIZED)

        login(request, user)
        return Response({"detail": "ログインしました。"}, status=status.HTTP_200_OK)


class LogoutAPIView(APIView):
    permission_classes = [permissions.IsAuthenticated]

    def post(self, request):
        logout(request)
        return Response({"detail": "ログアウトしました。"}, status=status.HTTP_200_OK)

書籍情報用View

sample_app/views/book.py
from rest_framework import viewsets

from sample_app.models import BookModel
from sample_app.serializers.book import BookSerializer


class BookViewSet(viewsets.ReadOnlyModelViewSet):
    queryset = BookModel.objects.order_by("id")
    serializer_class = BookSerializer

ユーザーごとの読書状態管理用View

sample_app/views/user_book.py
from rest_framework import permissions, viewsets

from sample_app.models import UserBookModel
from sample_app.serializers.user_book import UserBookSerializer


class UserBookViewSet(viewsets.ModelViewSet):
    serializer_class = UserBookSerializer
    permission_classes = [permissions.IsAuthenticated]

    def get_queryset(self):
        # ログイン中であるユーザー自身のデータだけ返す
        return UserBookModel.objects.filter(user=self.request.user).select_related("book", "user").order_by("-updated_at")

    def perform_create(self, serializer):
        serializer.save(user=self.request.user)

Router実装

sample_app/urls.py
from django.urls import path
from rest_framework.routers import DefaultRouter

from sample_app.views.auth import LoginAPIView, LogoutAPIView, RegisterAPIView
from sample_app.views.book import BookViewSet
from sample_app.views.user_book import UserBookViewSet

router = DefaultRouter()
router.register("books", BookViewSet, basename="books")
router.register("user-books", UserBookViewSet, basename="user-books")

urlpatterns = [
    path("auth/register/", RegisterAPIView.as_view(), name="auth-register"),
    path("auth/login/", LoginAPIView.as_view(), name="auth-login"),
    path("auth/logout/", LogoutAPIView.as_view(), name="auth-logout"),
]
urlpatterns += router.urls
sample_config/urls.py
from django.conf import settings
from django.contrib import admin
from django.urls import include, path

urlpatterns = [
    path('admin/', admin.site.urls),
    path("api/v1/", include("sample_app.urls")),
]

if settings.DEBUG:
    urlpatterns += [
        path("api/v1/", include("rest_framework.urls")),
    ]

URL追加後、http://127.0.0.1:8000/api/v1/にアクセスすることで、実装したAPIをDRF機能で実行可能です。

ApiRoot.png

まとめ

今回はDjango REST Frameworkを使った簡単な読書管理APIを作成しました。
今回のサンプルをベースにJWT認証logging設定などを追加していくことで、より実用的なAPIに発展させることができると思います。

ご拝読いただきありがとうございました!

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?