Slide 1

Slide 1 text

Django Ninja による API 開発効率化 とリプレースの実践 PyCon JP 2025 / @kashew_nuts 1 / 47

Slide 2

Slide 2 text

お品書き 自己/会社紹介 発表の動機・ゴール Django Ninjaの概要 + 選定理由 Django Ninjaを使ったAPI開発 導入後の変化・得られた知見 まとめ 2 / 47

Slide 3

Slide 3 text

Who am I? / お前誰よ 吉田花春 (Yoshida Kashun) 𝕏 @kashew_nuts BeProud Inc. Software Developer 環境整備と岩登りが趣味 3 / 47

Slide 4

Slide 4 text

What's BeProud Python メインで受託開発・研修・自社サービスの運営 私は主に connpass の開発/運営をしています フルリモートワーキング (5days/week) 4 / 47

Slide 5

Slide 5 text

プロジェクト背景・課題 10年以上続くDjangoプロジェクトで外部公開APIを運用 過去 django-piston → 独自実装 → 文脈負債 手動メンテによる API ドキュメント作成の煩雑さ 新機能やAPIの追加が予定、将来の拡張性への懸念が増大 [1] https://django-piston-sheepdog.readthedocs.io/en/latest/ 5 / 47

Slide 6

Slide 6 text

要件(目標) 型=ドキュメントの一元管理&自動化 既存 Django アプリに最小の影響で導入 v1 と v2 を共存 認証や多層スロットリングを導入し運用耐性を向上 6 / 47

Slide 7

Slide 7 text

この発表のゴール Django Ninjaを活用したAPI開発の特徴を知る Django REST frameworkやFastAPIとの比較情報を得る リプレース時の検討ポイントを学ぶ 7 / 47

Slide 8

Slide 8 text

想定する聞き手 DjangoでWeb APIを運用中の方 新しいAPIフレームワークを検討している方 高速かつ安定したAPI基盤を求める方 8 / 47

Slide 9

Slide 9 text

Django Ninja とは + 選定理由 https://django-ninja.dev/ 9 / 47

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

各パッケージ比較 1. FastAPI 2. Django REST Framework(DRF) 12 / 47

Slide 13

Slide 13 text

FastAPIの場合(概要) メリット: 高速でシンプル 一貫した非同期実行が可能(DB接続含む) 型ヒント(Pydantic)を活用したデータ検証&整形 デメリット: Djangoとの直接の親和性は低い Django ORMを使うにはDBコネクションの管理が冗長 開発ツールのセットアップがひと手間必要: REPL、テスト用DB 13 / 47

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

DRFの場合(概要) メリット: RESTのリソースとDjango Modelが1対1の場合、CRUDを作るのが簡単 豊富な実績とサードパーティーライブラリ OpenAPIドキュメントもdrf-spectacular を導入することで充実 デメリット: フレームワークが想定していない使い方をすると実装が複雑化する 型ヒント活用が標準では弱い ドキュメントを充実させるためにdrf_spectacularの記述がviews.pyに増える [1] https://pypi.org/project/drf-spectacular/ 15 / 47

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

Django Ninjaを用いた実践例 18 / 47

Slide 19

Slide 19 text

主要機能抜粋 OpenAPIドキュメントの自動生成 認証 / ページネーション / スロットリング モジュール分割(Routerの活用) 19 / 47

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

スキーマ定義を充実させて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

Slide 22

Slide 22 text

OpenAPIドキュメントのUI形式: Swagger UIとRedocの使い分け Swagger UI: インタラクティブな操作が可能。開発の動作確認で使いやすい。 22 / 47

Slide 23

Slide 23 text

OpenAPIドキュメントのUI形式: Swagger UIとRedocの使い分け Redoc: デザインが洗練されており、ドキュメントが読みやすいUX。 23 / 47

Slide 24

Slide 24 text

Django Ninjaを使ったAPI開発 | 認証 (概要) Django標準の認証や、拡張しやすいように認証クラスを用意している シンプルな実装なためカスマイズがしやすい 実際の内容はFastAPIのセキュリティ入門 も参考にしてください [1] https://fastapi.tiangolo.com/ja/tutorial/security/ 24 / 47

Slide 25

Slide 25 text

認証 (サンプル) 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

Slide 26

Slide 26 text

認証(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

Slide 27

Slide 27 text

認証(事例紹介) サードパーティと組み合わせた事例を参照。 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

Slide 28

Slide 28 text

ページネーション 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

Slide 29

Slide 29 text

スロットリング 比較的最近の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

Slide 30

Slide 30 text

モジュール分割(Routerを活用) 現実には1つのモジュールにすべてのロジックを収めるのは難しい。 →Routerを使うことで、複数のモジュールに分割可能 https://django-ninja.dev/guides/routers/ 30 / 47

Slide 31

Slide 31 text

モジュール分割(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

Slide 32

Slide 32 text

モジュール分割(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

Slide 33

Slide 33 text

モジュール分割(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

Slide 34

Slide 34 text

モジュール分割(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

Slide 35

Slide 35 text

テスト・運用時の注意 Django 標準の TestCase / pytest + pytest-django バージョン管理( api_v1 , api_v2 )や例外ハンドリング デコレーターで Django の機能を活かす(例: cache_page ) 35 / 47

Slide 36

Slide 36 text

テスト方法 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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

例外ハンドリング 独自の例外クラスごとにハンドラーを定義可能 @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

Slide 39

Slide 39 text

デコレーターの活用 Django標準やサードパーティのデコレーターを活用する from django.views.decorators.cache import cache_page from ninja.decorators import decorate_view @api.get("/test") # cache_page(5) #

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

段階的なリプレース戦略に伴う懸念 コミュニティ成熟度(DRF に比べると若い) 段階的移行やドキュメントの同期をどのように行うか 42 / 47

Slide 43

Slide 43 text

コミュニティ成熟度 Django Ninja自体のサードパーティパッケージは少ない しかしほしい機能は揃っており、カスタマイズのフックポイントも明確 Django自体のサードパーティは豊富なので、そちらを活用可能 参考情報がないかGitHub Issueや公式ドキュメントを確認することで対応 類似情報がないかFastAPIのドキュメントも確認 43 / 47

Slide 44

Slide 44 text

段階移行やドキュメントの同期 Step1: 新規APIをDjango Ninjaで書き換えて検証 Step2: 期待通りに動作することを確認 リクエスト/レスポンスの扱い。E2Eテストが通るか。 APIドキュメントの生成 Step3: 既存APIをすべてDjango Ninjaで書き換え Step4: APIドキュメントもDjango Ninjaに切り替え 44 / 47

Slide 45

Slide 45 text

副次的効果 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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

47 / 47