Share
Embed
Django Ninja による API 開発効率化とリプレースの実践
Kashun Yoshida
September 26, 2025
Transcript
47 slides
-
Django Ninja による API 開発効率化 とリプレースの実践 PyCon JP 2025 /
@kashew_nuts 1 / 47 -
Who am I? / お前誰よ 吉田花春 (Yoshida Kashun) 𝕏 @kashew_nuts
BeProud Inc. Software Developer 環境整備と岩登りが趣味 3 / 47 -
プロジェクト背景・課題 10年以上続くDjangoプロジェクトで外部公開APIを運用 過去 django-piston → 独自実装 → 文脈負債 手動メンテによる API
ドキュメント作成の煩雑さ 新機能やAPIの追加が予定、将来の拡張性への懸念が増大 [1] https://django-piston-sheepdog.readthedocs.io/en/latest/ 5 / 47 -
Django Ninja の概要 FastAPI に強く影響を受けた、DjangoのREST API作成用フレームワーク 最新バージョンはv1.4.3 型ヒント(Pydantic )を活用したデータ検証&整形 OpenAPIドキュメントの自動生成
(Swagger UI / Redoc) Django との親和性(ORM、認証、Middleware、サードパーティパッケージ、ASGI 等) [1] https://fastapi.tiangolo.com/ja/ [2] https://docs.pydantic.dev/latest/ 10 / 47 -
Django Ninja の概要(サンプル) from ninja import NinjaAPI, Schema api =
NinjaAPI() class Hello(Schema): message: str @api.get("/hello", response=Hello) def hello(request): return {"message": "world"} 11 / 47 -
FastAPIの場合(サンプル) from fastapi import FastAPI from pydantic import BaseModel app
= FastAPI() class Hello(BaseModel): message: str @app.get("/hello", response_model=Hello) def hello(): return {"message": "world"} 14 / 47 -
DRFの場合(概要) メリット: RESTのリソースとDjango Modelが1対1の場合、CRUDを作るのが簡単 豊富な実績とサードパーティーライブラリ OpenAPIドキュメントもdrf-spectacular を導入することで充実 デメリット: フレームワークが想定していない使い方をすると実装が複雑化する 型ヒント活用が標準では弱い
ドキュメントを充実させるためにdrf_spectacularの記述がviews.pyに増える [1] https://pypi.org/project/drf-spectacular/ 15 / 47 -
DRFの場合(サンプル) from drf_spectacular.utils import OpenApiExample, extend_schema from rest_framework.views import APIView
from rest_framework.response import Response class Hello(APIView): @extend_schema( # drf_spectacularでOpenAPIドキュメントを記述 request=HelloRequestSerializer, responses=HelloResponseSerializer, examples=[OpenApiExample("name", value=...), ...] ) def get(self, request): return Response({"message": "world"}) 16 / 47 -
Django Ninja/ FastAPI / Django REST Framework(DRF)比較 「Django 資産 ×
Pydanticを使った型 ×Docs」で最小摩擦かつ最大効率→ Django Ninja 観点 Django Ninja FastAPI Django REST Framework(DRF) Django 統合 ◎ △ ◎ 型ヒント ◎ (Pydantic) ◎ (Pydantic) △ (Serializer 中心) Docs 自動化 ◎ ◎ △ (drf-spectacularを利用) 学習コスト ◦ ◦ △ 並走移行 ◎ ◦ ◦ ASGI ◦ ◎ × 17 / 47 -
OpenAPIドキュメントの自動生成 API のエンドポイント、パラメーター、および応答について説明 実装とドキュメントのスキーマ定義を常に最新化 OpenAPIドキュメントをSwagger UI or Redocで自動生成 from ninja
import Redoc api = NinjaAPI( title="Demo API", description="This is a demo API with dynamic OpenAPI info section" docs=Redoc(), # デフォルトはSwagger() ) https://django-ninja.dev/guides/api-docs/ 20 / 47 -
スキーマ定義を充実させてOpenAPIドキュメントと連携 Fieldにtitleやdescription、exampleを記載 リクエストやレスポンスにスキーマを記載してデータ変換&OpenAPIに反映 from ninja import Field, Schema class UserSchema(Schema):
username: str = Field(..., title="ユーザー名", examples=["kashew"]) email: str = Field(..., title="Email", examples=["[email protected]"]) @api.get("/users/", response=UserSchema) def create_user(request, payload: UserSchema): user = User.objects.create(**payload.dict()) return user https://django-ninja.dev/guides/response/ 21 / 47 -
Django Ninjaを使ったAPI開発 | 認証 (概要) Django標準の認証や、拡張しやすいように認証クラスを用意している シンプルな実装なためカスマイズがしやすい 実際の内容はFastAPIのセキュリティ入門 も参考にしてください [1]
https://fastapi.tiangolo.com/ja/tutorial/security/ 24 / 47 -
認証 (サンプル) Djangoの認証を活用した場合 from ninja.security import django_auth api = NinjaAPI(auth=django_auth)
@api.get("/secure") def secure_endpoint(request): return {"message": f"Hello {request.auth}"} # -> {"message": "Hello kashew"} https://django-ninja.dev/guides/authentication/ 25 / 47 -
認証(API キー) APIキーをヘッダーに付与して認証する場合 from ninja.security import APIKeyHeader class APIKeyAuth(APIKeyHeader): param_name
= "X-API-Key" def authenticate(self, request, key): return Client.objects.filter(key=key, revoke=False).first() @api.get("/headerkey", auth=ApiKey()) def apikey(request): assert isinstance(request.auth, Client) return f"Hello {request.auth}" https://django-ninja.dev/guides/authentication/#api-key 26 / 47 -
認証(事例紹介) サードパーティと組み合わせた事例を参照。 JSON Web Token(JWT): Simple JWTと組み合わせ OAuth2: django-oauth-toolkit と組み合わせ
[1] https://github.com/vitalik/django-ninja/issues/45#issuecomment-1049829818 [2] https://github.com/vitalik/django-ninja/issues/1015#issuecomment-1899359588 27 / 47 -
ページネーション limit/offset、もしくはpage単位でシンプルに実装 DjangoのPaginatorや他ライブラリとも併用可能 from ninja.pagination import paginate @api.get("/users", response=list[UserSchema]) @paginate
def get_users(request): return User.objects.all() # -> /api/users?limit=10&offset=0 https://django-ninja.dev/guides/response/pagination/ 28 / 47 -
スロットリング 比較的最近のv1.2.0で同梱された。 IPやユーザー単位でのレート制限を実装 from ninja.throttling import AnonRateThrottle, AuthRateThrottle api =
NinjaAPI( throttle=[ AnonRateThrottle("10/s"), # IP単位。認証されていないユーザ向け。 AuthRateThrottle("100/s"), # 認証したユーザー単位 ], ) https://django-ninja.dev/guides/throttling/ 29 / 47 -
モジュール分割(Routerを活用) 複数のDjangoアプリケーションで構成されているケース → それぞれのDjangoアプリにAPIを追加したい ├── myproject │ └── settings.py ├──
events/ │ ├── __init__.py │ └── models.py ├── groups/ │ ├── __init__.py │ └── models.py ├── users/ │ ├── __init__.py │ └── models.py └── manage.py 31 / 47 -
モジュール分割(Routerを活用) APIを追加するには、各アプリケーションにapi.pyモジュールを作成 ├── myproject │ └── settings.py ├── events/ │
├── __init__.py │ ├── api.py │ └── models.py ├── groups/ │ ├── __init__.py │ ├── api.py │ └── models.py ├── users/ │ ├── __init__.py │ ├── api.py │ └── models.py └── manage.py 32 / 47 -
モジュール分割(Routerを活用) NinjaAPIクラスの代わりに、Routerクラスを使用 from ninja import Router from .models import Event
router = Router() # <- not NinjaAPI @router.get("/") def list_events(request): return [ {"id": e.id, "title": e.title} for e in Event.objects.all() ] @router.get("/{event_id}") def event_details(request, event_id: int): event = Event.objects.get(id=event_id) return {"title": event.title, "details": event.details} 33 / 47 -
モジュール分割(Routerを活用) 各モジュールから全Routerをimportし、メインのNinjaAPIインスタンスに取り込む ├── myproject │ ├── api.py │ └── settings.py
├── events/ │ ... from ninja import NinjaAPI api = NinjaAPI() # 認証やスロットリングも一括管理できる api.add_router("/events/", "events.api.router") # by Python path # ... 34 / 47 -
テスト・運用時の注意 Django 標準の TestCase / pytest + pytest-django バージョン管理( api_v1
, api_v2 )や例外ハンドリング デコレーターで Django の機能を活かす(例: cache_page ) 35 / 47 -
テスト方法 Ninjaには django.test.TestCase ベースのテストクライアントがある もちろんDjango標準のテストクライアントやpytest + pytest-django も使用可能 個人的には pytest
+ pytest-django がオススメ # pytestの例 import pytest from django.urls import reverse @pytest.mark.django_db def test_create_user(client): target_url = reverse("api-1.0.0:create_user") payload = {"id": 1, "username": "Alice", "email": "[email protected]"} response = client.get(target_url, payload) assert response.status_code == 200 assert "user_id" in response.json() 36 / 47 -
APIのバージョン管理 1つのDjangoプロジェクトから複数のAPIバージョンを作成可能 NinjaAPIインスタンスを2つ以上、異なるバージョン引数で作成する api_v1 = NinjaAPI(title="API V1", version="1.0.0") api_v2 =
NinjaAPI(title="API V2", version="2.0.0") # -> http://127.0.0.1/api/v1/docs @api_v1.get("/users") def get_users_v1(request): return [...] # -> http://127.0.0.1/api/v2/docs @api_v2.get("/users") def get_users_v2(request): return [...] https://django-ninja.dev/guides/versioning/ 37 / 47 -
例外ハンドリング 独自の例外クラスごとにハンドラーを定義可能 @api.exception_handler(ServiceUnavailableError) def service_unavailable(request, exc): return api.create_response(request, {"message": "Please
retry later"}, 503) @api.get("/service") # some logic that throws exception def some_operation(request): if random.choice([True, False]): raise ServiceUnavailableError() # some logic that throws exception return {"message": "Hello"} https://django-ninja.dev/guides/errors/ 38 / 47 -
デコレーターの活用 Django標準やサードパーティのデコレーターを活用する from django.views.decorators.cache import cache_page from ninja.decorators import decorate_view
@api.get("/test") # cache_page(5) # <!-- will not work @decorate_view(cache_page(5)) # It works! def test_view(request): return str(datetime.now()) https://django-ninja.dev/whatsnew_v1/ 39 / 47 -
CORS対応 django-cors-headers を併用 フロントエンドとの連携をスムーズに # settings.py INSTALLED_APPS += ["corsheaders",] MIDDLEWARE
+= [ # ... "corsheaders.middleware.CorsMiddleware" "django.middleware.common.CommonMiddleware", # ... ] # 必要に応じてスコープを制限 CORS_ALLOW_ALL_ORIGINS = True https://pypi.org/project/django-cors-headers/ 40 / 47 -
Appendix: クラスベースでの対応 Django Ninja Extra: Ninjaのサードパーティを活用することで可能 from ninja_extra import api_controller,
ControllerBase, permissions, route @api_controller("users/") class UsersController(ControllerBase): @route.get('', response={200: list[UserSchema]}) def get_users(self): users = User.objects.all() return users https://pypi.org/project/django-ninja-extra/ 41 / 47 -
段階移行やドキュメントの同期 Step1: 新規APIをDjango Ninjaで書き換えて検証 Step2: 期待通りに動作することを確認 リクエスト/レスポンスの扱い。E2Eテストが通るか。 APIドキュメントの生成 Step3: 既存APIをすべてDjango
Ninjaで書き換え Step4: APIドキュメントもDjango Ninjaに切り替え 44 / 47 -
副次的効果 Before: ファットコントローラー気味 After: ビジネスロジックの呼び出しに絞り記述がすっきり 既存の非効率なクエリも見直し、API高速化 ビジネスロジックに絞ったテストも実行しやすくなった # views.py router
= Router(tags=["イベント"]) # 1. decorator で紐づけ:エンドポイント、 レスポンススキーマ、サマリーなど @router.get("/", response=list[EventOut], summary="一覧", ...) # 2. リクエスト情報を関数の引数に付与 def event_search(request, query_params: Query[EventIn]): # 3. 検索処理(既存のDjangoロジック活用) events = event_search_api(**query_params.dict()) return events 45 / 47 -
まとめ & 今後の展望 Django Ninja 導入の3大メリット i. Django 資産の最大活用(設定や ORM
をそのまま使える) ii. 型ヒント & 自動ドキュメントによる保守性向上 iii. パフォーマンス面も良好、学習コストも低め リプレースの結果開発効率が上がり、運用負荷が低減した Django Ninjaのユースケース増加により事例拡大を期待 46 / 47