2024 iThome 鐵人賽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 的路由設定。

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 函式。