Python | Web フレームワーク:API 実践

Python
スポンサーリンク

概要(API 実践=「画面なしでデータをやり取りする Web アプリ」)

API 実践は、「ブラウザに HTML を返す」のではなく、「プログラム同士がデータをやり取りする窓口」を作る練習です。
Django なら、Django REST Framework を使うのが王道ですが、まずは「API とは何をしているのか」を、素の Django で体感してから、DRF に進むと理解が深くなります。

ここでは、次の流れで話します。
API のイメージと基本ルール(HTTP メソッドと JSON)
超シンプルな「ToDo API」を素の Django で作る
同じものを Django REST Framework で作るとどう変わるか
実践で意識すべきポイント(バリデーション、エラー、認証)

一つひとつ、コード付きでつなげていきます。


API の基本イメージをつかむ(HTML ではなく JSON を返す)

「人間向けの画面」ではなく「機械向けの返事」

普通の Django ビューは、テンプレートを使って HTML を返します。
API のビューは、HTML の代わりに JSON を返します。

例えば、ToDo の一覧を HTML で返すならこうです。

def todo_list_page(request):
    todos = Todo.objects.all()
    return render(request, "todo/list.html", {"todos": todos})
Python

同じデータを API として返すなら、こうなります。

from django.http import JsonResponse

def todo_list_api(request):
    todos = Todo.objects.all().values("id", "title", "is_done")
    return JsonResponse(list(todos), safe=False)
Python

ブラウザでアクセスすると、
前者は「人間が読むための画面」、
後者は「プログラムが読むための JSON テキスト」が返ってきます。

API 実践では、この「JSON を返す側」を作るのがメインです。

HTTP メソッドと CRUD の対応

API では、HTTP メソッドと CRUD の対応がほぼお約束になっています。

GET は「データを読む」
POST は「新しく作る」
PUT / PATCH は「更新する」
DELETE は「削除する」

例えば、ToDo API なら、こんな感じの設計になります。

GET /api/todos/ で一覧取得
POST /api/todos/ で新規作成
GET /api/todos/1/ で 1 件取得
PATCH /api/todos/1/ で一部更新
DELETE /api/todos/1/ で削除

この「URL とメソッドの組み合わせで意味を分ける」感覚が、API 実践の土台になります。


素の Django で「超シンプル ToDo API」を作る

モデルは普通の Django モデルでよい

まずはモデルです。
Web アプリ実践で使ったような ToDo モデルをそのまま使えます。

# todo/models.py
from django.db import models
from django.contrib.auth.models import User

class Todo(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    title = models.CharField(max_length=200)
    is_done = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.title
Python

API だからといって、特別なモデルは不要です。
「データの形」はいつも通り models.py に書きます。

一覧取得 API(GET /api/todos/)

URL から決めます。

# todo/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path("api/todos/", views.todo_list_api, name="todo_list_api"),
]
Python

ビューはこう書きます。

# todo/views.py
from django.http import JsonResponse
from django.contrib.auth.decorators import login_required
from .models import Todo

@login_required
def todo_list_api(request):
    if request.method != "GET":
        return JsonResponse({"detail": "Method not allowed"}, status=405)

    todos = Todo.objects.filter(user=request.user).values("id", "title", "is_done", "created_at")
    return JsonResponse(list(todos), safe=False)
Python

ここでのポイントは三つです。

JsonResponse を使って JSON を返していること。
values() で辞書のリストに変換し、それを list() にして渡していること。
login_required で「自分の ToDo だけ」を返すようにしていること。

safe=False は、「リストをそのまま JSON にしてよい」という意味です(デフォルトは辞書のみ許可)。

ブラウザで /api/todos/ にアクセスすると、
こんな JSON が返ってきます。

[
  {"id": 1, "title": "牛乳を買う", "is_done": false, "created_at": "2025-01-19T05:00:00Z"},
  {"id": 2, "title": "メール返信", "is_done": true, "created_at": "2025-01-19T06:00:00Z"}
]

新規作成 API(POST /api/todos/)

同じ URL に対して、POST を受け付けるようにします。
JSON で { "title": "新しいタスク" } が送られてくる想定です。

import json
from django.views.decorators.csrf import csrf_exempt

@csrf_exempt  # 実運用ではトークンやトークン認証を使う方が安全
@login_required
def todo_list_api(request):
    if request.method == "GET":
        todos = Todo.objects.filter(user=request.user).values("id", "title", "is_done", "created_at")
        return JsonResponse(list(todos), safe=False)

    if request.method == "POST":
        try:
            data = json.loads(request.body.decode("utf-8"))
        except json.JSONDecodeError:
            return JsonResponse({"detail": "Invalid JSON"}, status=400)

        title = (data.get("title") or "").strip()
        if not title:
            return JsonResponse({"detail": "title is required"}, status=400)

        todo = Todo.objects.create(user=request.user, title=title)
        return JsonResponse(
            {"id": todo.id, "title": todo.title, "is_done": todo.is_done, "created_at": todo.created_at},
            status=201,
        )

    return JsonResponse({"detail": "Method not allowed"}, status=405)
Python

ここで重要なのは、「API ならでは」の部分です。

request.body から JSON をパースしていること。
入力値をチェックし、足りなければ 400 Bad Request を返していること。
成功時に 201 Created を返していること。

HTML フォームと違い、API では「どんな JSON が来るか分からない」前提なので、
バリデーションとエラーレスポンスがとても大事になります。

1 件取得・更新・削除 API

URL を分けて、1 件ずつ扱うエンドポイントも作ります。

# todo/urls.py
urlpatterns = [
    path("api/todos/", views.todo_list_api, name="todo_list_api"),
    path("api/todos/<int:pk>/", views.todo_detail_api, name="todo_detail_api"),
]
Python

ビューはこうです。

from django.shortcuts import get_object_or_404

@csrf_exempt
@login_required
def todo_detail_api(request, pk):
    todo = get_object_or_404(Todo, id=pk, user=request.user)

    if request.method == "GET":
        return JsonResponse(
            {"id": todo.id, "title": todo.title, "is_done": todo.is_done, "created_at": todo.created_at}
        )

    if request.method in ["PUT", "PATCH"]:
        try:
            data = json.loads(request.body.decode("utf-8"))
        except json.JSONDecodeError:
            return JsonResponse({"detail": "Invalid JSON"}, status=400)

        title = data.get("title")
        if title is not None:
            title = title.strip()
            if not title:
                return JsonResponse({"detail": "title cannot be empty"}, status=400)
            todo.title = title

        is_done = data.get("is_done")
        if is_done is not None:
            todo.is_done = bool(is_done)

        todo.save()
        return JsonResponse(
            {"id": todo.id, "title": todo.title, "is_done": todo.is_done, "created_at": todo.created_at}
        )

    if request.method == "DELETE":
        todo.delete()
        return JsonResponse({"detail": "deleted"}, status=204)

    return JsonResponse({"detail": "Method not allowed"}, status=405)
Python

ここで深掘りしたいのは、「HTTP メソッドと意味の対応」と「エラー処理」です。

GET は「そのまま返すだけ」。
PUT / PATCH は「送られてきたフィールドだけ更新する」。
DELETE は「消して 204 No Content を返す」。

そして、存在しない id のときは 404 を返す(get_object_or_404)。
JSON が壊れていたら 400。
許可していないメソッドなら 405。

このあたりを丁寧にやると、「ちゃんとした API」になります。


Django REST Framework を使うとどう楽になるか

シリアライザで「モデル ⇔ JSON」の変換を任せる

素の Django で API を書くと、
毎回「JSON をパースして、バリデーションして、辞書にして返す」コードが増えます。

Django REST Framework(DRF)を使うと、
このあたりをかなり自動化できます。

まず、シリアライザを定義します。

# todo/serializers.py
from rest_framework import serializers
from .models import Todo

class TodoSerializer(serializers.ModelSerializer):
    class Meta:
        model = Todo
        fields = ["id", "title", "is_done", "created_at"]
        read_only_fields = ["id", "created_at"]
Python

これで、「Todo モデルを JSON に変換する」「JSON から Todo を作る」処理を
DRF に任せられます。

ViewSet とルーターで CRUD を一気に定義する

DRF の ViewSet を使うと、
一覧・作成・取得・更新・削除をまとめて書けます。

# todo/api_views.py
from rest_framework import viewsets, permissions
from .models import Todo
from .serializers import TodoSerializer

class TodoViewSet(viewsets.ModelViewSet):
    serializer_class = TodoSerializer
    permission_classes = [permissions.IsAuthenticated]

    def get_queryset(self):
        return Todo.objects.filter(user=self.request.user)

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

URL はルーターで自動生成できます。

# todo/api_urls.py
from rest_framework.routers import DefaultRouter
from .api_views import TodoViewSet

router = DefaultRouter()
router.register("todos", TodoViewSet, basename="todo")

urlpatterns = router.urls
Python

プロジェクト側の urls.py で、これを /api/ 以下にぶら下げます。

# config/urls.py
from django.urls import path, include

urlpatterns = [
    path("api/", include("todo.api_urls")),
]
Python

これだけで、次のエンドポイントが自動で生まれます。

GET /api/todos/
POST /api/todos/
GET /api/todos/{id}/
PUT /api/todos/{id}/
PATCH /api/todos/{id}/
DELETE /api/todos/{id}/

しかも、バリデーションや 400/404/405 の処理も、
かなりの部分を DRF がやってくれます。

「素の Django で一度 API を手書きしてみてから、DRF に乗り換える」と、
DRF が何を肩代わりしてくれているのかがよく分かります。


実践で意識したい API のポイント

バリデーションとエラーレスポンスを丁寧にする

API は「人間ではなくプログラムが相手」です。
だからこそ、「何がダメなのか」を機械的に分かる形で返す必要があります。

例えば、title が空のときに

ただ 500 エラーを出すのではなく
400 Bad Request として
{“detail”: “title is required”} のような JSON を返す

といった丁寧さが、クライアント側の実装を楽にします。

DRF の Serializer を使うと、
このあたりのバリデーションとエラーメッセージを
かなりきれいに書けるようになります。

認証と権限を必ず考える

API は、ブラウザだけでなく他のサービスやアプリからも叩かれます。
だからこそ、「誰が叩いているのか」「その人に何を許すのか」を
きちんと決める必要があります。

今回の ToDo API では、
login_required や IsAuthenticated を使って
「ログインしているユーザーだけ」「自分の ToDo だけ」
という制限をかけました。

より実践的には、
トークン認証や JWT、API キーなどを使って
「ブラウザ以外のクライアント」からのアクセスも
安全に扱うことになります。

最初の一歩としては、

認証なしの「誰でも触れる API」は基本作らない
request.user を軸にデータを絞る

この二つを強く意識しておくとよいです。


まとめ(API 実践は「画面のない Web アプリ」を作る練習)

API 実践を初心者目線でまとめると、こうなります。

API は「HTML の代わりに JSON を返す Web アプリ」であり、HTTP メソッド(GET/POST/PUT/PATCH/DELETE)と URL の組み合わせで CRUD を表現する。
素の Django でも JsonResponse と request.body を使えば API は作れるが、バリデーションやエラー処理を自前で書く必要があり、その大変さを一度体感してから Django REST Framework に進むと理解が深まる。
Django REST Framework を使うと、Serializer と ViewSet と Router で「モデル ⇔ JSON」「CRUD エンドポイント」「認証・権限」をかなり自動化できるが、根っこにあるのは「request.user を軸にデータを返す」「正しいステータスコードとエラーメッセージを返す」というシンプルな考え方。

タイトルとURLをコピーしました