Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Django Ninja による API 開発効率化とリプレースの実践

Django Ninja による API 開発効率化とリプレースの実践

Avatar for Kashun Yoshida

Kashun Yoshida

September 26, 2025
Tweet

More Decks by Kashun Yoshida

Other Decks in Programming

Transcript

  1. Who am I? / お前誰よ 吉田花春 (Yoshida Kashun) 𝕏 @kashew_nuts

    BeProud Inc. Software Developer 環境整備と岩登りが趣味 3 / 47
  2. プロジェクト背景・課題 10年以上続くDjangoプロジェクトで外部公開APIを運用 過去 django-piston → 独自実装 → 文脈負債 手動メンテによる API

    ドキュメント作成の煩雑さ 新機能やAPIの追加が予定、将来の拡張性への懸念が増大 [1] https://django-piston-sheepdog.readthedocs.io/en/latest/ 5 / 47
  3. 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
  4. 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
  5. 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
  6. 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
  7. 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
  8. 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
  9. スキーマ定義を充実させて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
  10. 認証 (サンプル) 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
  11. 認証(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
  12. 認証(事例紹介) サードパーティと組み合わせた事例を参照。 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
  13. スロットリング 比較的最近の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
  14. モジュール分割(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
  15. モジュール分割(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
  16. モジュール分割(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
  17. モジュール分割(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
  18. テスト・運用時の注意 Django 標準の TestCase / pytest + pytest-django バージョン管理( api_v1

    , api_v2 )や例外ハンドリング デコレーターで Django の機能を活かす(例: cache_page ) 35 / 47
  19. テスト方法 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
  20. 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
  21. 例外ハンドリング 独自の例外クラスごとにハンドラーを定義可能 @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
  22. デコレーターの活用 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
  23. 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
  24. 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
  25. 副次的効果 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
  26. まとめ & 今後の展望 Django Ninja 導入の3大メリット i. Django 資産の最大活用(設定や ORM

    をそのまま使える) ii. 型ヒント & 自動ドキュメントによる保守性向上 iii. パフォーマンス面も良好、学習コストも低め リプレースの結果開発効率が上がり、運用負荷が低減した Django Ninjaのユースケース増加により事例拡大を期待 46 / 47