概要(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
PythonAPI だからといって、特別なモデルは不要です。
「データの形」はいつも通り 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)
PythonURL はルーターで自動生成できます。
# 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 を軸にデータを返す」「正しいステータスコードとエラーメッセージを返す」というシンプルな考え方。
