nikkie-ftnextの日記

イベントレポートや読書メモを発信

Django Ninja 素振りの記:CreateとReadをシュババと実装🥷

はじめに

七尾百合子さん、お誕生日 70日目 おめでとうございます! nikkieです。

オンラインで開催しているDjangoもくもく会で取り組みました1

目次

Django Ninja 🥷

PyCon JP 2024DjangoCongress JP 2025 でたびたびその名を見かけていました

DjangoFastAPIっぽくAPIを書けるという理解です。
手を動かしてどんなものか掴みたく、もくもくネタに積んでいました2

Hello World

これは爆速でした3

django-ninja.dev

やったこと

  1. django-ninjaのインストール
    • 今回uvでやっています
  2. Djangoプロジェクト作成
    • uv run django-admin startproject myproject
    • Djangoアプリケーションを作っていないので、Hello Worldは爆速となっています
  3. プロジェクトにapi.pyを作り、urls.pyに指定

myproject/api.py

from ninja import NinjaAPI

api = NinjaAPI()


@api.get("/hello")
def hello(request):
    return "Hello world"

request引数は、Djangoのビュー関数っぽいですよね

CRUDのうち、ReadとCreateを実装

チュートリアルHello Worldで終わりです。
DjangoCongress JP 2025の私の発表で作った書籍アプリに育てようと思いました。

Djangoアプリケーション導入

一歩目としてアプリケーションを作ります(uv run myproject/manage.py startapp book_api)。
プロジェクトのapi.pyはアプリケーション側に移しました。
アプリケーションを作ったことで、ドキュメントのCRUD exampleがピンと来るようになりました。
Djangoモデルを作ればいいのです!

django-ninja.dev

Read

GET /books で書籍全件を返します。
CRUD exampleの「List of objects」より、Djangoモデルのobjects.all()を返せばよいです。

book_api/models.py

class Book(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    isbn = models.CharField(max_length=17, unique=True)
    title = models.CharField(max_length=255)
    page = models.PositiveIntegerField()

book_api/api.py

@api.get("/books", response=list[BookOut])
def get_books(request):
    books_qs = Book.objects.all()
    return books_qs

その時に指定するのが、デコレータのresponse引数。
これでレスポンススキーマを指定しています。

django-ninja.dev

レスポンススキーマは自分で定義しています。
FastAPIでもやる、PydanticのBaseModelを継承したクラスのようですね4

from ninja import Schema


class BookOut(Schema):
    id: UUID
    isbn: str
    title: str
    page: int

FastAPIでも経験した5ように、Djangoモデル(ORM)の返すクエリセットをレスポンススキーマに合わせてDjango Ninjaが変換してくれているという理解です。

Create

POST /books で書籍データを新規作成します。
CRUD exampleの「Create」より、クライアントから送られてくるデータをパースすると理解しました。

チュートリアルの中の「Input from the request body」から、入力データのスキーマを定義します。

book_api/api.py

class BookIn(Schema):
    isbn: str
    title: str
    page: int

UUIDはユーザが指定するのではなく、システム側でDB保存時に自動採番としています(モデルのdefault=uuid.uuid4)。

book_api/api.py

@api.post("/books", response={201: BookOut})
def create_book(request, payload: BookIn):
    book = Book.objects.create(**payload.dict())
    return book

ビュー関数の返り値をレスポンススキーマに変換するだけでなく、ユーザが送信してきたデータもリクエストのスキーマにNinjaが変換してくれるのは大変便利ですね。
書くコードがぐっと減りました。

201 Createdで返したかったので、responseキーがステータスコード(整数)、値がレスポンススキーマ辞書を指定しました。
ref: https://django-ninja.dev/tutorial/step3/#multiple-response-types

E2Eして、FastAPI版と同じと確認

DBをPostgreSQLに設定して、Djangoマイグレーションを実行。
E2Eを実行します6

% gauge run specs
Python: 3.12.8
# Bookを登録できる
  ## /booksにPOSTリクエストを送って、Bookを登録できる    ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔

# Bookを取得できる
  ## Bookが1冊も登録されていないとき、/booksへのGETリクエストには200が返る       ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔

  ## Bookが登録されているとき、/booksにGETリクエストを送って、Bookを一覧できる    ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔

Specifications: 2 executed      2 passed        0 failed        0 skipped
Scenarios:      3 executed      3 passed        0 failed        0 skipped

Total time taken: 563ms

FastAPI製書籍アプリで通ったテストは、Django Ninja製書籍アプリでも通り続けました🙌

今回Django Ninjaで実装したアプリの全容はこちらです

宿題事項

限られた時間(1時間半程度)でDjango Ninjaを触るため、後回しにした事項が多々あります。

  • Djangoの便利なライブラリとの連携
    • 例えば、django-environ7(練習なので割り切って、settings.pyにハードコードしています)
  • FastAPIと比較するなら async / await はどうなる?
  • Djangoモデルに加えて、入力と出力で同じようなBookクラスを書きました(全3つ)
    • SQLModelが解決しようとしている8問題だと思いますが、Django Ninjaでは何か解決策があるのでしょうか?
  • BookOutpydantic.Fieldを使って例示できる?(自動生成ドキュメントをリッチにしたい)

終わりに

Django Ninja、同期のCreateとReadを実装しました。
Djangoモデルを用意して、リクエストとレスポンスのスキーマを定義するだけで、これらを実装できました!
DBまわりはDjangoがカバーしてくれている分、FastAPIより楽ですね9
これでパフォーマンスもFastAPIに近いのであれば、DRFの代わりに選択されうると思います

最後に宣伝。
Djangoもくもく会、次回は6/29(日)です!

pythonista-books.connpass.com


  1. 頭ミリオンを発揮
  2. FastAPIのHello Worldにも思うんですが、Hello Worldが劇的に速く作れるというのは(ユーザに価値を届けるアプリケーションを速く作れるかを意味しないので)私への訴求としては弱いですね
  3. ninja.Schemapydantic.BaseModelを継承しています。ref: https://github.com/vitalik/django-ninja/blob/v1.4.1/ninja/schema.py#L209
  4. 直近の取り組みはここで使うためでした
  5. たしかmultiple model Multiple Models with FastAPI - SQLModel
  6. FastAPIではいくつかのライブラリを知る必要がありました