コンテンツにスキップ

Django Ninjaのルーティング#

2024 iThome鉄人レース

これはDjango Ninjaシリーズチュートリアルの第8回です。

前回の記事では、Djangoの従来のルーティング設定方法を紹介しました。

前述の通り、「ルーティングリスト」があることは確かに良いことです。しかし、プロジェクトの規模が拡大するにつれて、urls.pyviews.pyの間を絶え間なく行き来することは、開発者の認知的負荷を大幅に増加させます。開発時間が長くなるだけでなく、エラーも発生しやすくなります。

Django Ninjaは、FlaskやFastAPIの設計理念を組み合わせた、よりモダンなルーティング設計を採用しています。ルーティングの定義を簡素化するだけでなく、コードの可読性を向上させ、ルーティングとview関数を密接に結合させます。

サンプルプロジェクトの進捗#

本記事におけるルーティング設定に関するコードの変更は、こちらのPRPull Request)を参照してください。

サンプルプロジェクトは本記事のPRをマージした後、正式に「APIプロジェクト」となりましたが、現在(このcommitの状態を指します)はまだ正常に動作しません。なぜなら、view関数の基本機能をまだ完成させていないからです。

各記事のPRを一つずつ追いかけることで、その回の新しい内容を学ぶことができます。これが私が記事ごとにPRを作成している理由です。

GitHubサンプルプロジェクト#

👉 Django-Ninja-Tutorial


さて、Django Ninjaのルーティング設定の紹介を始めましょう。

Django Ninjaルーティングの概要#

Django NinjaはPythonのデコレータ(decorator)を使用してルーティングとHTTPメソッドを定義します。この方法により、ルーティングとview関数が密接に結合され、コードの可読性が大きく向上します。

Pythonに慣れている方ならご存知の通り、実はこの「デコレータを使用してルーティングを定義する」という方法は、元々はFlaskから来たものです。軽量フレームワークであるFlaskは、この簡潔で優雅な設計をいち早く導入し、模範的な画期的アイデアと言えます。

この設計はその後、FastAPIや本記事のDjango Ninjaなど、他のフレームワークにも採用され、この柔軟なルーティング定義のパターンが引き継がれました。

Flaskでは、開発者は次のようにルーティングを定義できます。

from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello():
    return 'Hello, Flask!'

Django Ninjaも似たような概念を採用していますが、構文的にはDjangoのエコシステムにより溶け込んでおり、型ヒント(型ヒント)とPydanticのデータ検証機能を組み合わせて、API開発をさらにモダンなものにしています。

以下はDjango Ninjaの簡単な例です。

from ninja import NinjaAPI

api = NinjaAPI()

@api.get('/')
def hello(request):
    return {"message": "Hello, Django Ninja!"}

FlaskとDjango Ninjaのこの2つの書き方は、非常に似ているというより、全く同じと言っても過言ではありません😎


より組織的なアプローチ:Routerオブジェクトの使用#

先ほどの例のように、NinjaAPIを直接使用してルーティングを定義するのは簡単で直感的です。しかし実際の業務では、異なるDjango appのルーティングを管理するためにRouterオブジェクト(公式ドキュメント)を使用することを強くお勧めします。

from ninja import Router

router = Router()  # Routerオブジェクトを作成

@router.get(path='/')
def hello(request):
    return {"message": "Hello, Django Ninja!"}

これは、Djangoの従来のアプローチにおける「1次ルーティングと2次ルーティングを区別する」という基本精神と一致しています。プロジェクトの構造を明確に保つだけでなく、各appのロジックを独立させることができます。

Django Ninjaにおいて、Routerオブジェクトはモジュール化されたルーティング設定方法を提供し、各Django appが自身のルーティングを管理できるようにします。そして、プロジェクトレベルのapi.pyそれらを一つに統合します。これは、従来のurls.pyの機能を置き換えるものです。

以下のコード例では、すべてRouterオブジェクトを使用して実装します。


プロジェクト構造の変化#

Django Ninjaを採用した後、従来のDjangoプロジェクトの構造がどのように変化するかを見てみましょう。

Djangoの従来のルーティング構造#

サンプルプロジェクトを例にとると、これは従来のDjangoの典型的な構造です。

├── NinjaForum
   ├── urls.py  # プロジェクトの1次ルーティング
   ├── ...
├── post
   ├── urls.py  # appの2次ルーティング
   ├── view.py  # appに属するview関数を配置する場所
   ├── ...
├── user
   ├── urls.py  # appの2次ルーティング
   ├── view.py  # appに属するview関数を配置する場所
   ├── ...
├── ...

Django appレベルのurls.pyがすべてのapp内のルーティングを定義し、その後プロジェクトのurls.pyによって統合されます。秩序立っており、権限と責任が明確に分かれています。

Django Ninjaのルーティング構造#

Django Ninjaを採用した後、プロジェクトの構造にはいくつかの変化が生じます。以下は典型的なDjango Ninjaプロジェクトの構造です。

├── NinjaForum
   ├── urls.py  # プロジェクトの「ゼロ次」ルーティング
   ├── api.py   # プロジェクトの1次ルーティング
   ├── ...
├── post
   ├── api.py   # post appのルーティング + view関数
   ├── ...
├── user
   ├── api.py   # user appのルーティング + view関数
   ├── ...
├── ...

この構造では、各Django appは1つのapi.pyを持ち、そのappのすべてのAPIルーティングとview関数を定義するために使用されます。これは、従来のDjangoにおけるurls.pyviews.pyの機能を置き換えるものです。

プロジェクトレベルのapi.pyは、すべてのDjango appのAPIを統合する役割を果たします。

さらに、プロジェクトのurls.py依然として必要です。これは、Django NinjaのAPIルーティングをDjangoのURL設定に再び統合する役割を担っています。同時に、「ゼロ次」ルーティングとして機能し、すべてのAPIに/api/のようなプロジェクト全体で統一されたルーティングプレフィックスを追加することもできます。


Django Ninjaルーティングの実装#

Django Ninjaのルーティング構造を理解したところで、サンプルプロジェクトの2つのDjango appで「すべてのユーザーを取得する」と「記事リストを取得する」の2つのAPIをそれぞれ実装してみましょう。

今後の数回の記事で、段階的にこれらのAPIを完成させていきます。現在はまだ雛形に過ぎませんので、まずはルーティング設定に焦点を当てましょう。

一、2次ルーティングの作成#

user app内にapi.pyを作成し、以下の内容を記述します。

# user/api.py
from ninja import Router

router = Router()

@router.get(path='/')
def get_users(request):
    users = User.objects.all()
    return users

同様に、post/api.pyにも似たようなルーティングとview関数を作成します。

# post/api.py
from ninja import Router

router = Router()

@router.get(path='/')
def get_posts(request):
    posts = Post.objects.all()
    return posts

このようにして、userとpostの2つのappそれぞれにAPIを作成しました。次に、これらのルーティングをプロジェクトレベルのAPIルーティングに統合する必要があります。

二、1次ルーティングの作成#

Djangoプロジェクトディレクトリ(NinjaForumディレクトリを指します)の下に、api.pyを作成する必要があります。これは私たちの1次ルーティングとして機能し、すべてのappのAPIを統合します。以下はこのapi.pyの内容です。

# NinjaForum/api.py
from ninja import NinjaAPI

api = NinjaAPI()

api.add_router(prefix='/users/', router='user.api.router')
api.add_router(prefix='/posts/', router='post.api.router')

注意すべき点として、ここでのルーティングの統合には2つの書き方があり、上記は私が普段使用している書き方です。

もう1つの書き方は、直接routerオブジェクトをimportするものです。

from user.api import router as user_router
from post.api import router as post_router

api.add_router(prefix='/users/', router=user_router)
api.add_router(prefix='/posts/', router=post_router)

この2つの方法は機能的には同等であり、どちらを選択するかは主に個人の好みやプロジェクトの組織方法によります。

三、プロジェクトのurls.py#

Django Ninjaにおいて、プロジェクトレベルのurls.pyは、DjangoとDjango Ninja APIを繋ぐ架け橋となります。

プロジェクトのurls.pyでは、プロジェクト全体で共有されるルーティングプレフィックスをさらに定義することができます。以下のようになります。

from django.contrib import admin
from django.urls import path

from NinjaForum.api import api

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', api.urls),
]

ここではプロジェクトのルーティングプレフィックス、/api/を定義しています。

これにより、「すべての記事を取得する」APIエンドポイントは以下のようになります。

/api/posts/

もちろん、追加のルーティングプレフィックスが必要ない場合は、直接省略することもできます。

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', api.urls),  # プレフィックスを省略
]

これで、Django Ninjaのルーティング設定は完了です。

このような構造は、Django本来のモジュール化された設計を保ちながら、私たちのAPI開発により大きな柔軟性を提供してくれます。


本節のまとめと次へのステップ#

第1節では、Django Ninjaを使用してルーティングを定義する方法を学び、Djangoの従来のルーティングとDjango Ninjaのルーティングの違いを理解しました。

Django Ninjaのルーティングアプローチは、コードの可読性を高めるだけでなく、プロジェクトの明確な構造を維持し、Djangoの従来のルーティングのいくつかの欠点を改善します。

次回は、Django Ninja APIのコア部分であるview関数に入っていきます。