2024 iThome 鐵人賽2024 iThome 鐵人賽

這是 Django Ninja 系列教學的第 8 篇。

上一篇文章中,我們介紹了 Django 傳統的路由設定方式。

如前所述,雖然有一個「路由清單」確實不錯。但隨著專案規模的擴大,不斷來回切換urls.pyviews.py將大幅增加開發者的認知負擔——拉長了開發時間,還更容易導致錯誤。

Django Ninja 採用了一種更現代化的路由設計,結合了 Flask 和 FastAPI 的設計理念。不僅簡化了路由的定義,還提升了程式碼的可讀性,讓路由與 view 函式緊密結合

範例專案動態

本文關於路由設定的程式碼改動,可以參考這個 PRPull Request)。

範例專案合併了本文的 PR 後,已經正式成為一個「API 專案」,不過它目前(指這個 commit 狀態)尚無法正常運作,因為我們還沒有完善 view 函式的基本功能。

你可以一步步地跟著每一篇的 PR,來學習當次的新內容。這也是我為文章建立 PR 的用意所在。


現在,我們開始介紹 Django Ninja 的路由設定。

Django Ninja 路由概述

Django Ninja 使用 Python 裝飾器(decorator)來定義路由和 HTTP 方法。這種方式將路由與 view 函式緊密結合,大大提高了程式碼的可讀性。

熟悉 Python 的都知道,其實這種「使用裝飾器定義路由」的方式,最早來自 Flask。作為一個輕量級框架,Flask 率先引入了這種簡潔優雅的設計,堪稱典範級的創舉

這個設計後續被其他框架採用,比如 FastAPI 和本文的 Django Ninja,都繼承了這種靈活的路由定義模式。

在 Flask 中,開發者可以這樣定義路由:

1
2
3
4
5
6
7
from flask import Flask

app = Flask(__name__)

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

Django Ninja 也採用了類似的概念,只是在語法上更融入 Django 生態,並結合了型別提示(type hint)和 Pydantic 的資料驗證功能,讓 API 開發變得更加現代化。

以下是 Django Ninja 的一個簡單範例:

1
2
3
4
5
6
7
from ninja import NinjaAPI

api = NinjaAPI()

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

Flask 與 Django Ninja 這兩種寫法,與其說非常相似,不如說一模一樣😎


更有組織的做法:使用 Router 物件

雖然直接使用上述範例中的NinjaAPI來定義路由簡單而直觀。但實際工作中,我們更推薦使用Router物件(官方文件)來管理不同 Django app 的路由。

1
2
3
4
5
6
7
from ninja import Router

router = Router() # 建立 Router 物件

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

這和 Django 傳統做法中「區分一級與二級路由」的基本精神相符。不僅讓專案的架構保持清晰,還能讓每個 app 的邏輯獨立。

在 Django Ninja 中,Router物件提供了一種模組化的路由設定方式,讓每個 Django app 可以管理自己的路由,並在專案層級的api.py統一整合起來——也就是取代傳統的urls.py功能。

以下程式碼範例,我們都會以Router物件來實作。


專案架構變化

先來看看採用 Django Ninja 以後,傳統 Django 專案的結構會有怎麼樣的變化

Django 傳統路由結構

以範例專案為例,這是傳統 Django 的典型結構:

1
2
3
4
5
6
7
8
9
10
11
12
├── NinjaForum
│ ├── urls.py # 專案一級路由
│ ├── ...
├── post
│ ├── urls.py # app 二級路由
│ ├── view.py # 放置 app 所屬 view 函式的地方
│ ├── ...
├── user
│ ├── urls.py # app 二級路由
│ ├── view.py # 放置 app 所屬 view 函式的地方
│ ├── ...
├── ...

Django app 層級的urls.py負責定義所有 app 內的路由,然後再由專案的urls.py進行統合。井然有序,權責分明。

Django Ninja 路由結構

在採用 Django Ninja 後,專案結構會有一些變化。以下是一個典型的 Django Ninja 專案結構:

1
2
3
4
5
6
7
8
9
10
11
├── NinjaForum
│ ├── urls.py # 專案「零級」路由
│ ├── api.py # 專案一級路由
│ ├── ...
├── post
│ ├── api.py # post app 的路由 + view 函式
│ ├── ...
├── user
│ ├── api.py # user app 的路由 + view 函式
│ ├── ...
├── ...

在這個結構中,每個 Django app 都有一個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 的路由結構,我們直接在範例專案的兩個 Django app 分別實作「取得所有使用者」和「取得文章列表」兩個 API。

我們會在接下來的數篇文章中,循序漸進地完善這些 API。目前只是雛形,先把焦點放在路由設定上。

一、建立二級路由

在 user app 中建立一個api.py, 內容為:

1
2
3
4
5
6
7
8
9
# 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 函式:

1
2
3
4
5
6
7
8
9
# post/api.py
from ninja import Router

router = Router()

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

這樣,我們就為 user 和 post 兩個 app 分別建立了 API。接下來,我們需要將這些路由整合到專案級別的 API 路由中。

二、建立一級路由

在 Django 專案目錄(指 NinjaForum 目錄)底下,我們也需要建立一個api.py。它將作為我們的一級路由,整合所有 app 的 API。以下是這個api.py的內容:

1
2
3
4
5
6
7
# 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')

值得留意的是,這裡的路由整合有兩種寫法,上面我習慣使用的。

另一種寫法是直接 import router物件:

1
2
3
4
5
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)

這兩種方法在功能上是等效的,選擇哪種主要取決於個人偏好和專案的組織方式。

三、專案 urls.py

在 Django Ninja 中,專案層級的urls.py化身為連接 Django 和 Django Ninja API 的橋梁

在專案urls.py中,我們還能再定義全專案共用的路由前綴。長這樣:

1
2
3
4
5
6
7
8
9
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 端點將會是:

1
/api/posts/

當然,如果你不需要額外的路由前綴,也可以直接省略:

1
2
3
4
urlpatterns = [
path('admin/', admin.site.urls),
path('', api.urls), # 省略前綴
]

至此,我們完成了 Django Ninja 的路由設定。

這樣的結構不僅保持了 Django 原有的模組化設計,還為我們的 API 開發提供了更大的靈活性。


本節收尾與下一步

在第一節中,我們學習了如何使用 Django Ninja 定義路由,並了解 Django 傳統路由與 Django Ninja 路由的差異。

Django Ninja 的路由做法不僅讓程式碼更具可讀性,還保持了專案的清晰結構,改善了 Django 傳統路由的一些缺點。

下一篇,我們將進入 Django Ninja API 的核心部分——view 函式。