Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

プロジェクト背景・課題 10年以上続くDjangoプロジェクトで外部公開APIを運用 API を独自フレームワークで実装している中での課題 初期はdjango-pistonを使用。メンテされなくなったのでリプレースしていた 手動による API ドキュメント作成など煩雑さ 今後の機能拡張を見据え、安定性と性能を担保しつつ将来の拡張性を確保したい 5 / 56

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

Django Ninja とは + 選定理由 8 / 56

Slide 9

Slide 9 text

Django Ninja の概要 FastAPIに強く影響を受けた、DjangoのREST API作成用フレームワーク 最新バージョンはv1.3.0 型ヒント(Pydantic)を活用したデータ検証&整形 OpenAPIドキュメントの自動生成 (Swagger UI / Redoc) Django との親和性(ORM、Middleware、サードパーティパッケージ、ASGI等) 9 / 56

Slide 10

Slide 10 text

Django Ninja の概要(サンプル) from ninja import NinjaAPI, Schema api = NinjaAPI() class User(Schema): id: int username: str @app.get("/users/", response=list[User]) def get_users(request): return [{"id": 1, "username": "kashew"}] # -> /api/users/ 10 / 56

Slide 11

Slide 11 text

各パッケージ比較 実際に比較した候補 1. Django REST framework(DRF) 2. FastAPI 3. Django Ninja 11 / 56

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

DRFの場合(サンプル) from rest_framework.views import APIView class UserListAPIView(APIView): @extend_schema( # drf_spectacularでOpenAPIドキュメントを記述 request=UserRequestSerializer, responses=UserResponseSerializer, examples=[ OpenApiExample("ID", value=...), OpenApiExample("name", value=...), ... ], ), def get(self, request): return [{"id": 1, "username": "kashew"}] 13 / 56

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

FastAPIの場合(サンプル) from fastapi import FastAPI from pydantic import BaseModel, Field app = FastAPI() class User(BaseModel): id: int = Field(..., title="ID", examples=[1]) username: str = Field(..., title="ユーザー名", examples=["kashew"]) @app.get("/users/", response_model=list[User]) def get_users(): return [{"id": 1, "username": "kashew"}] # -> /api/users/ 15 / 56

Slide 16

Slide 16 text

Django Ninja/ FastAPI / Django REST Framework(DRF)比較 Django Ninja FastAPI DRF シンプルな設計と 高速なパフォーマ ンス Django の機能を活かし つつ、型ヒントを活用 した直感的な開発 型ヒントを活用し 直感的な開発 Django の機能を 活用しながら API を構築 型ヒントで自動バ リデーションとド キュメント生成 Pydantic を利用した容 易なバリデーション Pydantic を利用し た容易なバリデー ション 独自のシリアラ イザーを利用 Django とのシーム レスな統合 シームレスな統合 Django の統合が 難しい シームレスな統 合 16 / 56

Slide 17

Slide 17 text

「なぜ Django Ninja を選んだのか」の要点 既存 Django プロジェクトを最大限活用したい 型ヒントドリブンな実装が将来の機能拡張に有効と判断 パフォーマンス/学習コスト共に良好(PoCで確認) 17 / 56

Slide 18

Slide 18 text

採用時の懸念・対策 懸念 コミュニティ成熟度(DRF に比べると若い) 段階的移行やドキュメントの同期をどのように行うか 対策・結果 PoC でクリアにした 実装の拡張性と可読性が大きく向上 18 / 56

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

Django Ninja を使った実装のポイント 21 / 56

Slide 22

Slide 22 text

セットアップ & ルーティング $ pip install django-ninja Djangoのurls.pyにルーティング情報を追加 from django.urls import path from ninja import NinjaAPI api = NinjaAPI() @api.get("/hello") def hello(request): return {"message": "Hello World"} urlpatterns = [ path("api/", api.urls), ] 22 / 56

Slide 23

Slide 23 text

主要機能 型ヒント&Pydanticを活用したスキーマ定義 OpenAPIドキュメントの自動生成 認証 / ページネーション / スロットリング モジュール分割(Routerの活用) 23 / 56

Slide 24

Slide 24 text

型ヒント&Pydanticを活用したスキーマ定義 Pydanticとは: Python の型ヒントを活用して、データを検証できるライブラリー 堅牢なバリデーションが可能 スキーマにFieldを定義することで細やかなカスタマイズが可能 24 / 56

Slide 25

Slide 25 text

Django NinjaがPydanticを使ってバリデーションする例 # schemas.py >> from ninja import Field, Schema # SchemaはPydanticのBaseModelを継承 >> class UserSchema(Schema): >> id: int >> name: str = Field(alias="username") # <- aliasを定義 >>> UserSchema(id=1, name="kashew") >>> print(user) UserSchema(id=1, name='kashew') >>> UserSchema(id="not number", name="kashew") # intにstrを指定するとエラーが発生 ... 中略 ... pydantic_core._pydantic_core.ValidationError: 1 validation error for UserSchema id Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='not number', input_type=str] 25 / 56

Slide 26

Slide 26 text

OpenAPIドキュメントの自動生成 /api/docs にアクセス 実装とドキュメントのスキーマ定義を常に最新化 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() ) 26 / 56

Slide 27

Slide 27 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 27 / 56

Slide 28

Slide 28 text

OpenAPIとは 仕様と仕様を元に処理するツール、生成したドキュメントが存在する OpenAPI仕様(OpenAPI Specification: OAS) API のエンドポイント、パラメーター、および応答について説明する形式 適切に設計され、一貫して文書化された API を作成できる OpenAPIドキュメント OpenAPI仕様を読み込んで生成したドキュメント SwaggerUIやRedoc形式がある [1] https://swagger.io/specification/ 28 / 56

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 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"} 32 / 56

Slide 33

Slide 33 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 33 / 56

Slide 34

Slide 34 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 34 / 56

Slide 35

Slide 35 text

スロットリング 比較的最近のv1.2.0で同梱された。 IPやユーザー単位でのレート制限を実装 from ninja.throttling import AnonRateThrottle, AuthRateThrottle api = NinjaAPI( throttle=[ AnonRateThrottle("10/s"), # IP単位。認証されていないユーザ向け。 AuthRateThrottle("100/s"), # 認証したユーザー単位 ], ) 35 / 56

Slide 36

Slide 36 text

モジュール分割(Routerを活用) 現実には1つのモジュールにすべてのロジックを収めるのは難しい。 →Routerを使うことで、複数のモジュールに分割可能 36 / 56

Slide 37

Slide 37 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 37 / 56

Slide 38

Slide 38 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 38 / 56

Slide 39

Slide 39 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} 39 / 56

Slide 40

Slide 40 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 # ... 40 / 56

Slide 41

Slide 41 text

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

Slide 42

Slide 42 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.post(target_url, payload) assert response.status_code == 200 assert "user_id" in response.json() 42 / 56

Slide 43

Slide 43 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 [...] 43 / 56

Slide 44

Slide 44 text

CORS対応 django-cors-headers を併用 フロントエンドとの連携をスムーズに # settings.py INSTALLED_APPS += ["corsheaders",] MIDDLEWARE += [ # ... "corsheaders.middleware.CorsMiddleware" "django.middleware.common.CommonMiddleware", # ... ] # 必要に応じてスコープを制限 CORS_ALLOW_ALL_ORIGINS = True 44 / 56

Slide 45

Slide 45 text

例外ハンドリング 独自の例外クラスごとにハンドラーを定義可能 @api.exception_handler(ServiceUnavailableError) def service_unavailable(request, exc): return api.create_response( request, {"message": "Please retry later"}, status=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"} 45 / 56

Slide 46

Slide 46 text

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

Slide 47

Slide 47 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 47 / 56

Slide 48

Slide 48 text

導入後の変化・得られた知見 48 / 56

Slide 49

Slide 49 text

導入後の変化 Before / After API ドキュメント: 手動 HTML → OpenAPI 自動生成 ビューの可読性: 独自実装 → デコレーター & Schema でスッキリ JSONP 廃止: 実際の利用状況をログで確認 → 不要と判断 49 / 56

Slide 50

Slide 50 text

導入後の変化 1:API ドキュメント編 Before: HTML タグを手動で作成 After: Django Ninja で OpenAPI ドキュメントを自動生成 50 / 56

Slide 51

Slide 51 text

導入後の変化 2:view の記述 Before: ファットコントローラー気味 After: 3 項目ぐらいになり、記述がスッキリ decorator で紐づけ:エンドポイント、 レスポンススキーマ、サマリーなど リクエスト情報を関数の引数に付与 ビジネスロジックの呼び出し from ninja import Query, Router router = Router(tags=["イベント"]) @router.get("/", response=EventListOut, summary="一覧", ...) def event_search(request, query_params: Query[EventIn]): events = event_search_api(**query_params.dict()) return events 51 / 56

Slide 52

Slide 52 text

導入後の変化 3:廃止したものも Before: ブラウザからも使えるように、JSONP をサポートしていた After: JSONP は廃止した (実は)有料API化により固定IPが必要になり、事実上使えなくなっていた ログを確認してアクセスがないことを確認して廃止を決めた [1] https://e-words.jp/w/JSONP.html 52 / 56

Slide 53

Slide 53 text

まとめ & 今後の展望 Django Ninja 導入の3大メリット i. Django 資産の最大活用(設定や ORM をそのまま使える) ii. 型ヒント & 自動ドキュメントによる保守性向上 iii. パフォーマンス面も良好、学習コストも低め リプレースの結果、開発・運用負荷が低減した 今後認証の仕組みを導入し、使い始めるための手間を減らしていく Django Ninjaのユースケース増加によりコミュニティ拡大を期待 53 / 56

Slide 54

Slide 54 text

参考URL Django Ninja公式ドキュメント https://django-ninja.dev/ Pydantic公式ドキュメント https://docs.pydantic.dev/latest/ FastAPI公式ドキュメント https://fastapi.tiangolo.com/ja/ 54 / 56

Slide 55

Slide 55 text

55 / 56

Slide 56

Slide 56 text

Any Question ? 56 / 56