# 3.3 エラーハンドリングの実装パターン(Django REST Framework 編)
API 設計において エラーハンドリングの統一 は非常に重要。
レスポンス形式がバラバラだとフロント側での処理が複雑になり、ユーザー体験も悪化する。
この記事では、Django REST Framework(DRF)を使ったエラーハンドリングの実装パターンを整理する。
1. バリデーションエラー
Serializer の validate_*
や validate
を使うと、入力チェックでエラーを返せる。
class OrderInputSerializer(serializers.ModelSerializer):
class Meta:
model = Order
fields = ["customer_name", "total_amount"]
def validate_total_amount(self, value):
if value <= 0:
raise serializers.ValidationError("金額は 0 より大きい必要があります。")
return value
リクエスト例:
{ "customer_name": "株式会社テスト", "total_amount": -100 }
レスポンス例(400 Bad Request):
{
"error": "ValidationError",
"message": {
"total_amount": ["金額は 0 より大きい必要があります。"]
}
}
2. 認証・権限エラー
ログインしていない場合や権限不足のときは DRF が自動で 401 / 403 を返す。
これもフォーマットを統一して返したい。
レスポンス例(403 Forbidden):
{
"error": "PermissionDenied",
"message": "この操作を実行する権限がありません。"
}
3. 業務ロジックエラー
業務システム特有の「状態による制御」もエラーとして扱う必要がある。
例: 「承認済みの受注は修正できない」
from rest_framework.exceptions import APIException
class BusinessLogicError(APIException):
status_code = 409
default_detail = "業務ロジックエラー"
default_code = "business_error"
def update_order(order, user):
if order.status == "APPROVED":
raise BusinessLogicError("承認済みの受注は修正できません")
レスポンス例(409 Conflict):
{
"error": "BusinessLogicError",
"message": "承認済みの受注は修正できません"
}
4. 想定外エラーの統一
サーバー側の例外(例: DB エラー)をそのまま返すのは危険。
共通の例外ハンドラを定義して、返却形式を統一する。
# utils/exceptions.py
from rest_framework.views import exception_handler
from rest_framework.response import Response
def custom_exception_handler(exc, context):
response = exception_handler(exc, context)
if response is not None:
# DRF 標準のエラーをラップ
return Response({
"error": exc.__class__.__name__,
"message": response.data
}, status=response.status_code)
# 想定外のエラー
return Response({
"error": "ServerError",
"message": "予期しないエラーが発生しました。"
}, status=500)
設定に組み込む:
# settings.py
REST_FRAMEWORK = {
"EXCEPTION_HANDLER": "core.utils.exceptions.custom_exception_handler"
}
5. 共通レスポンス形式を決める
プロジェクトではあらかじめ「エラーはこの形式で返す」とルールを決めておくと良い。
例:
{
"error": "ValidationError",
"message": {
"field": ["エラーメッセージ"]
}
}
{
"error": "PermissionDenied",
"message": "この操作を実行する権限がありません。"
}
{
"error": "BusinessLogicError",
"message": "承認済みの受注は修正できません"
}
{
"error": "ServerError",
"message": "予期しないエラーが発生しました。"
}
6. フロント側での扱いやすさを意識する
フロントエンドでは次のように使いやすくなる。
-
error
→ エラー種別で分岐(ValidationError, PermissionDenied, BusinessLogicError など) -
message
→ そのまま画面に表示できる
Vue 側のサンプル:
try {
await axios.post("/api/orders/", payload);
} catch (err) {
const error = err.response.data;
if (error.error === "ValidationError") {
showFormErrors(error.message);
} else {
alert(error.message);
}
}
さらに、axios の interceptor を使って共通化すると便利。
import axios from "axios";
import { showErrorDialog } from "@/components/core/ErrorDialog";
const api = axios.create({ baseURL: "/api" });
api.interceptors.response.use(
res => res,
err => {
const error = err.response?.data;
if (error) {
showErrorDialog(error.message);
} else {
showErrorDialog("予期しないエラーが発生しました");
}
return Promise.reject(err);
}
);
export default api;
これにより、全ての API エラーを統一的にハンドリングできる。
API 設計において エラーハンドリングの統一 は非常に重要。
レスポンス形式がバラバラだとフロント側での処理が複雑になり、ユーザー体験も悪化する。
この記事では、Django REST Framework(DRF)を使ったエラーハンドリングの実装パターンを整理する。
1. バリデーションエラー
Serializer の validate_*
や validate
を使うと、入力チェックでエラーを返せる。
class OrderInputSerializer(serializers.ModelSerializer):
class Meta:
model = Order
fields = ["customer_name", "total_amount"]
def validate_total_amount(self, value):
if value <= 0:
raise serializers.ValidationError("金額は 0 より大きい必要があります。")
return value
リクエスト例:
{ "customer_name": "株式会社テスト", "total_amount": -100 }
レスポンス例(400 Bad Request):
{
"error": "ValidationError",
"message": {
"total_amount": ["金額は 0 より大きい必要があります。"]
}
}
2. 認証・権限エラー
ログインしていない場合や権限不足のときは DRF が自動で 401 / 403 を返す。
これもフォーマットを統一して返したい。
レスポンス例(403 Forbidden):
{
"error": "PermissionDenied",
"message": "この操作を実行する権限がありません。"
}
3. 業務ロジックエラー
業務システム特有の「状態による制御」もエラーとして扱う必要がある。
例: 「承認済みの受注は修正できない」
from rest_framework.exceptions import APIException
class BusinessLogicError(APIException):
status_code = 409
default_detail = "業務ロジックエラー"
default_code = "business_error"
def update_order(order, user):
if order.status == "APPROVED":
raise BusinessLogicError("承認済みの受注は修正できません")
レスポンス例(409 Conflict):
{
"error": "BusinessLogicError",
"message": "承認済みの受注は修正できません"
}
4. 想定外エラーの統一
サーバー側の例外(例: DB エラー)をそのまま返すのは危険。
共通の例外ハンドラを定義して、返却形式を統一する。
# utils/exceptions.py
from rest_framework.views import exception_handler
from rest_framework.response import Response
def custom_exception_handler(exc, context):
response = exception_handler(exc, context)
if response is not None:
# DRF 標準のエラーをラップ
return Response({
"error": exc.__class__.__name__,
"message": response.data
}, status=response.status_code)
# 想定外のエラー
return Response({
"error": "ServerError",
"message": "予期しないエラーが発生しました。"
}, status=500)
設定に組み込む:
# settings.py
REST_FRAMEWORK = {
"EXCEPTION_HANDLER": "core.utils.exceptions.custom_exception_handler"
}
5. 共通レスポンス形式を決める
プロジェクトではあらかじめ「エラーはこの形式で返す」とルールを決めておくと良い。
例:
{
"error": "ValidationError",
"message": {
"field": ["エラーメッセージ"]
}
}
{
"error": "PermissionDenied",
"message": "この操作を実行する権限がありません。"
}
{
"error": "BusinessLogicError",
"message": "承認済みの受注は修正できません"
}
{
"error": "ServerError",
"message": "予期しないエラーが発生しました。"
}
6. フロント側での扱いやすさを意識する
フロントエンドでは次のように使いやすくなる。
-
error
→ エラー種別で分岐(ValidationError, PermissionDenied, BusinessLogicError など) -
message
→ そのまま画面に表示できる
Vue 側のサンプル:
try {
await axios.post("/api/orders/", payload);
} catch (err) {
const error = err.response.data;
if (error.error === "ValidationError") {
showFormErrors(error.message);
} else {
alert(error.message);
}
}
さらに、axios の interceptor を使って共通化すると便利。
import axios from "axios";
import { showErrorDialog } from "@/components/core/ErrorDialog";
const api = axios.create({ baseURL: "/api" });
api.interceptors.response.use(
res => res,
err => {
const error = err.response?.data;
if (error) {
showErrorDialog(error.message);
} else {
showErrorDialog("予期しないエラーが発生しました");
}
return Promise.reject(err);
}
);
export default api;
これにより、全ての API エラーを統一的にハンドリングできる。
AI活用のポイント
エラーハンドリングのコードは、AIに生成させるのが特に相性が良い領域。
ただし、使い方にはちょっとしたコツがある。
-
最初はおまかせで依頼する
「エラーハンドリングを統一したいからコードを出して」とざっくり指示すると、雛形はすぐ出てくる。 -
既存コードを提示して「これと同じように」と依頼する
プロジェクト独自の書き方がある場合は、過去のコードを渡して合わせてもらうのが一番早い。 -
うまくいかない場合だけ細かく指示する
返却フォーマットやステータスコードの扱いなど、AIが迷いやすい部分は必要に応じて個別に補足する。
このように「まずはAIに任せる → 必要に応じて修正指示」という流れにすることで、余計な手間をかけずにスピーディに仕組みを整えられる。
まとめ
DRF でのエラーハンドリングは以下を押さえるのがポイント。
- Serializer → バリデーションエラー
- 認証・権限エラー → 401 / 403
- 業務ロジックエラー → 409 / 422
- 想定外エラー → 共通ハンドラでラップ
- 返却形式を統一してフロントで処理しやすくする
- Vue 側では interceptor を使って共通的に扱う
そして AI活用 では:
- まずはおまかせでコードを生成させる
- 必要なら既存コードを例示して「同じ形式で」と依頼する
- どうしても揃わない部分だけ個別に指示する
このシンプルな使い方で、AIを活用しつつ、プロジェクト固有のルールに合わせたエラーハンドリングを実現できる。
次回は フィルタリングと検索の実装パターン を紹介する予定。
Discussion