Django Ninja:DRF 與 FastAPI 之外的第三條路
近幾年 Python 後端在 API 選型上常陷入兩難:Django REST Framework(DRF)常被嫌太重,寫起來充斥著樣板程式碼且序列化效能偏弱;FastAPI 開發手感俐落、型別提示完整,但抽離了 ORM、管理後台與遷移機制後,自建與拼裝基礎設施的維護成本往往超出預期。
許多轉向 FastAPI 的團隊在業務擴大後才體會到微框架的代價:失去 Django 累積二十年的成熟生態,包含自動化資料庫遷移(Migrations)、資安預設機制,以及營運必備的 Django Admin。
2020 年 Vitaliy Kucheryaviy(@vitalik)發起的 django-ninja 正好補上這塊空缺。其核心理念非常直截了當:如果能把 FastAPI 的精華(型別提示、Pydantic、自動化 OpenAPI、非同步支援)直接嫁接在 Django 的骨架上,何必拋棄 Django 累積多年的成熟生態?
它的定位很明確:保留 Django 原生 ORM、Admin 與遷移機制,但把 API 傳輸層換成現代的 Pydantic Schema、型別宣告與非同步支援,讓團隊在既有 Django 基礎上享有接近 FastAPI 的開發體驗。
一、DRF vs FastAPI vs django-ninja:三種架構的權衡光譜
技術選型取決於專案的商業邊界與維護成本。當前 Python API 開發形成了三大截然不同的陣營:
- Django + DRF:標準單體思維。以多重類別繼承消除重複程式碼,標準 CRUD 寫起來很省力,但業務一偏離 ViewSet 的約定就得層層覆寫,抽象堆疊後難以除錯。
- 純 FastAPI:現代微服務思維。專注於高效能 ASGI 傳輸與 API 閘道器,資料庫持久化、遷移與權限控管全由工程師自行挑選套件拼裝。
- Django + Ninja:現代實用主義。承認 Django 核心基礎設施的不可替代性,改用函式型 View(Function-Based Views,FBV)與宣告式 Schema 重構 API 傳輸層。
為了具體評估三者的工程差異,以下矩陣聚焦於決定選型的核心面向:
| 評估面向 | Django + DRF | Django + Ninja | 純 FastAPI |
|---|---|---|---|
| 傳輸與非同步協定 | 主要為 WSGI(非同步支援有限) | WSGI / ASGI 雙軌原生相容 | 原生 ASGI(Starlette 驅動) |
| View 架構典範 | Class-Based Views 重度繼承 | Function-Based Views 優先(可外掛控制器) | Function-Based Views 優先 |
| 資料驗證與轉換 | DRF Serializer(純 Python 反射) | Pydantic v2(Rust 驗證引擎) | Pydantic v2(Rust 驗證引擎) |
| 型別提示與 IDE DX | 差(欄位字串設定,缺乏提示) | 極佳(函式簽名與 Schema 嚴格型別化) | 極佳(函式簽名與 Schema 嚴格型別化) |
| 內建基礎設施 | 完整電池(Admin、ORM、Migrations) | 完整電池(Admin、ORM、Migrations) | 零內建(需外掛組裝或自建) |
| 序列化 CPU 效能 | 慢(大規模物件序列化成瓶頸) | 快(pydantic-core Rust 引擎) | 快(pydantic-core Rust 引擎) |
| 架構整合模式 | 深度綁定單體生態 | 原生 Django App,易於雙軌漸進遷移 | 獨立微服務,自行維護元件相容性 |
為什麼不乾脆用純 FastAPI?
既然 django-ninja 的寫法與 FastAPI 如此神似,為何不乾脆徹底跳出 Django?關鍵在於基礎設施與內部營運工具的建置成本。
純微服務或推薦運算節點確實適合 FastAPI 的輕快。但只要系統涉及營運、客服、財務與權限控管,開發者很快會被捲入基礎設施的建置成本中:
- Django Admin 的生產力槓桿:在 Django 裡定義好 Model 後,只要一行
admin.site.register,就能直接獲得具備分頁、篩選、搜尋與權限的後台。FastAPI 即使外掛 SQLAdmin,其成熟度與擴充彈性依然存在明顯差距,團隊常得花費數週重造輪子。 - 資料庫遷移(Migrations)的可靠度:Django 的
makemigrations與migrate是業界最成熟的關聯遷移工具。SQLAlchemy 搭配 Alembic 在處理欄位重新命名時,受限於工具的自動偵測限制,常需手寫 Python 遷移腳本,失誤風險較高。 - 開箱即用的認證與安全預設:Django 內建使用者模型、Session、權限群組與 CSRF 防護,密碼雜湊預設採用經過業界驗證的 PBKDF2,並支援 Argon2(需額外安裝套件啟用)。FastAPI 只提供通訊底層,認證、密碼儲存與權限模型全得自行挑選套件實作,品質取決於個別工程師的經驗。
django-ninja 讓團隊在保留這些成熟資產的同時,享有現代型別與序列化體驗。
反過來說,選 django-ninja 也放棄了三樣 FastAPI 才有的東西:內建的 Depends() 依賴注入(得靠 django-ninja-extra 補)、WebSocket(得另裝 Django Channels),以及 Starlette 的輕量請求週期。Django 每個請求仍要走完整套 middleware,純 I/O 轉發型的高並行情境,吞吐量還是輸給 FastAPI。
為什麼不再青睞 DRF?
相較於 DRF,django-ninja 在日常開發中帶來了質的改善。以最常見的「建立書籍端點(含輸入驗證與輸出格式化)」為例:
在 DRF 中的實作方式:
# serializers.py
from rest_framework import serializers
from .models import Book
class BookCreateSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = ['title', 'price', 'author_id']
def validate_price(self, value):
if value < 0:
raise serializers.ValidationError("價格不可為負數")
return value
# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
class BookCreateView(APIView):
def post(self, request):
serializer = BookCreateSerializer(data=request.data)
if serializer.is_valid():
book = serializer.save()
return Response({"id": book.id, "title": book.title}, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
在 django-ninja 中的實作方式:
from ninja import NinjaAPI, Schema, Field
from .models import Book
api = NinjaAPI()
class BookIn(Schema):
title: str
price: float = Field(..., ge=0)
author_id: int
class BookOut(Schema):
id: int
title: str
@api.post("/books", response={201: BookOut})
def create_book(request, data: BookIn):
book = Book.objects.create(**data.model_dump())
return 201, book
程式碼簡化背後反映了四項工程躍升:
- 宣告式自動前置驗證:DRF 需要在 Serializer 寫
validate_<field>並在 View 裡手動檢查serializer.is_valid();django-ninja 以 Pydantic Schema 作為資料契約與驗證規範的單一真實來源(Single Source of Truth),不符規格的請求在進 View 之前就會被擋下並回傳標準 422 錯誤。 - 自動型別傳遞與重構安全:
BookIn與BookOut是標準 Python 類別,IDE 能清楚解析型別;DRF 在fields = ['title', 'price']使用字串清單,重構時無法獲得靜態分析工具的保護。 - 宣告式路由取代繁瑣設定:DRF 在
APIView下需手動維護path,在ViewSet下又得依賴DefaultRouter的約定命名;裝飾器路由讓端點路徑、輸入規格與回應型別集中在同一處。 - 嚴謹的自動化 OpenAPI 3.x 契約:端點的輸入輸出由 Pydantic Schema 具體規範,系統啟動時能自動匯出與程式碼完全吻合的 OpenAPI 規格檔案,可直接串接 CI/CD 產出前端 TypeScript 定義,在編譯時期提早抓出前後端介面不一致的問題。
第四點值得多說兩句。DRF 官方文件已宣告內建的 OpenAPI 產生器棄用,改推薦第三方的 drf-spectacular;但它面對 APIView 或自訂 action 時常推斷不出回應格式,得再補 @extend_schema 手動標註,文件與程式碼容易脫節。django-ninja 的 Schema 本身就是文件來源,不需要第二套標註。
新專案技術選型決策路徑
綜合架構需求與維護成本,團隊可依循以下決策樹判定技術方向:
核心業務是否需要 Django 電池?
(Admin、複雜 ORM、內建 Auth)
+-- 否(純微服務 / 高並行閘道器 / WebSocket)
| +-- 選用純 FastAPI,搭配 SQLAlchemy 2.0 + asyncpg
+-- 是(需要完整商業基礎設施)
+-- 團隊是否已有龐大的 DRF 既有程式碼?
+-- 否(全新 Django 專案)
| +-- 首選 django-ninja:
| 最佳 DX、型別支援與 Pydantic 效能
+-- 是(維護數百個 DRF 舊端點)
+-- 新需求是否追求型別提示與高序列化效能?
+-- 是 > 雙軌並存:
既有維持 DRF,新功能接入 django-ninja
+-- 否 > 維持 DRF 現狀,
引入 drf-spectacular 補強 OpenAPI
二、序列化效能革命:從 Python 物件遞迴到 Rust 機器碼
API 請求的整體延遲主要由兩部分組成:資料庫 I/O 階段(查詢傳輸與驅動解析)與 CPU 階段(資料驗證與物件序列化)。
DRF:純 Python 遞迴的 CPU 瓶頸
DRF 的序列化機制本質上是一套純 Python 遞迴狀態機。面對 QuerySet 中的每筆 Model 實體,Serializer 必須透過反射(getattr)取出欄位,對應各欄位類別實體(如 CharField、DateTimeField),呼叫 to_representation() 建構字典,最後再呼叫 json.dumps() 產出字串。
當單次請求需要序列化 1,000 筆包含 20 個欄位的紀錄時,DRF 會在 Python 虛擬機器中建立並呼叫超過 20,000 次 Python 函式。在全域直譯器鎖(GIL)的限制下,單一 CPU 核心會被密集運算卡住,造成伺服器每秒請求數(RPS)急遽下滑。
Pydantic v2:把驗證與序列化交給 Rust
2023 年 11 月發布的 django-ninja 1.0 是一次重要躍進:底層全面升級至 Pydantic v2。
Pydantic v2 將核心驗證與序列化邏輯改交由 Rust 編寫的底層函式庫 pydantic-core 執行。當 Model 實體傳入 Schema 時,Pydantic 仍需透過 Python 逐一讀取屬性,但接下來的欄位驗證、型別轉換與 JSON 輸出全部在 Rust 內完成,省去了 DRF 每個欄位都要經過的多層 Python 方法呼叫。
DRF 序列化流程 (純 Python 遞迴,CPU 瓶頸):
Django Model
--> getattr 反射
--> Serializer 欄位類別實例
--> to_representation()
--> json.dumps()
[每千筆資料觸發逾 20,000 次 Python 函式呼叫,
GIL 強烈競爭]
django-ninja 序列化流程 (Pydantic v2 / Rust 引擎):
Django Model
--> from_attributes 讀取屬性
--> pydantic-core (Rust 驗證與 JSON 序列化)
--> JSON 字串
[屬性讀取仍在 Python 層,但欄位驗證、型別轉換與
JSON 輸出全在 Rust 完成,省去大量 Python 函式呼叫]
社群基準測試普遍顯示,Pydantic v2 的純 CPU 序列化速度是 DRF Serializer 的數倍。但端到端 API 的實際收益取決於序列化在整個請求中的占比:回傳大量紀錄的清單端點感受最明顯,而資料庫 I/O 主導的端點改善幅度會小得多。
三、發展現況:從實驗專案到穩定維護
截至 2026 年 9 月,django-ninja 主儲存庫累積超過 9,100 顆 Star 與 600 次 Fork,最近一次提交距今不到一週。創作者 Vitaliy Kucheryaviy 仍主導技術方向,新版 Python 與 Django 的相容工作則由社群貢獻者分擔。
版本里程碑
- 2020 年 v0.x:由 vitalik 發起,驗證「FastAPI 的寫法搬到 Django 上」的概念,奠定 Router、Pagination 與 Schema 體系。
- 2023 年 11 月 v1.0:全面升級 Pydantic v2,放棄 v1 相容包袱,引進
Annotated[]型別標記與非同步認證。 - 2026 年 8 月 v1.7.0:分頁加入
max_limit上限防止超大請求,並確認 Django 6.1 與 Python 3.14 相容,其中 Django 6.1 支援由前 Django Fellow felixxm 親自送 PR 確認。
相容範圍與維護節奏
PyPI 宣告的支援範圍涵蓋 Django 3.1 至 6.1、Python 3.7 至 3.14,1.7.0 並移除了 Django 版本上限限制,讓新版 Django 釋出時不必等 django-ninja 跟進就能安裝。
釋出節奏屬於穩健型:2026 年 3 月 1.6.2、8 月 1.6.3 與 1.7.0,之後接著 1.7.1 的預覽版。沒有激進的破壞性改版,1.0 之後的升級多半是小修與相容更新,對生產環境是好消息。
四、企業實務與遷移:程式碼治理、雙軌共存與生態邊界
對於擁有成熟業務的企業而言,架構不僅要跑得快,更要能平滑過渡與長期維護。
大型專案的程式碼治理:從 FBV 到控制器模式
早期 django-ninja 將所有端點宣告為純粹的函式(FBV):
@api.get("/orders/{order_id}")
def get_order(request, order_id: int):
...
這種風格在小專案足夠輕快,但專案一旦擴充到上百個實體,純 FBV 就會面臨分頁、排序等樣板程式碼分散重複的問題,且缺乏統一的依賴注入機制來管理服務客戶端。
社群套件 django-ninja-extra 為大型專案提供了結構化的物件導向增強層:
@api_controller模式:引進控制器類別,將關聯端點集中在單一結構內,支援在類別層級統一掛載權限與中介邏輯。- 依賴注入機制:整合
injector套件,在 View 建構子直接宣告服務相依性,提升單元測試時的 Mock 便利性。 ModelController:提供標準化 CRUD 控制器抽象,但依然保留 Pydantic Schema 的型別驗證,避免了 DRF 多重繼承的深層呼叫鏈。
對於習慣 DRF 的 Class-Based Views 或 NestJS/Spring 控制器模式的團隊,django-ninja-extra 能提供平滑的結構銜接;偏好現代微服務風格的團隊,也能維持原生 FBV 搭配 APIRouter 分組。
漸進式遷移策略:雙軌共存實務
對於維護數百個 DRF 端點的成熟團隊而言,推翻重寫是巨大的工程風險。django-ninja 能與 DRF 實現雙軌並存。
Django ROOT_URLCONF
+-- /api/v1/ > DRF DefaultRouter(既有端點,維護中)
+-- /api/v2/ > NinjaAPI(新功能與高效能重構端點)
兩者共用同一套 Django Models 與商業邏輯 Service
遷移實務可依循三個步驟:
- 路由層分流(URL Routing):在根目錄
urls.py中,將歷史端點維持於path("api/v1/", include(drf_router.urls)),將新端點掛載至path("api/v2/", ninja_api.urls),底層存取相同的 Django Models。 - 認證層打通(Shared Authentication):撰寫輕量的 Ninja
HttpBearer轉接類別,內部直接沿用 DRF SimpleJWT 的驗證後端(如UntypedToken),讓舊 Token 能透過新端點驗證。針對 Bearer Token 請求自動豁免 CSRF,Cookie 請求則維持防護。 - 策略性試水溫:優先挑選「純資料查詢、高頻存取、回傳大量資料」的報表或商品清單端點。這類端點在 DRF 中飽受純 Python 序列化所苦,遷移至 django-ninja 能立即看見效能效益。
周邊生態成熟度與邊界局限
評估生產就緒度時,周邊生態的成熟度直接關係到維護成本:
| 套件 / 領域 | 成熟度 | 生產建議與架構定位 |
|---|---|---|
django-ninja-extra | 生產就緒 | 提供 Class-Based Controllers 與依賴注入,適合大型專案減少樣板 |
django-ninja-jwt | 生產就緒 | 移植自 SimpleJWT,提供完整的 Token 簽發、更新與驗證端點 |
| 物件級權限 / 巢狀寫入 | 需手動實作 | 缺乏如 django-guardian 或 drf-writable-nested 的現成生態,需手寫檢查與外鍵關聯處理 |
除了上述擴充外,官方內建的 FilterSchema 與分頁機制已足夠完善。但若系統涉及複雜的物件級權限(Object-level Permissions)或多層主從資料表連動寫入,目前仍需開發者在 View 或 Service 層手動撰寫映射邏輯。
非同步的但書:SynchronousOnlyOperation
django-ninja 支援 async def 端點,但 Django ORM 底層的資料庫驅動仍是同步的。為了防止阻塞式查詢卡住事件迴圈(Event Loop),Django 只要在執行中的事件迴圈偵測到同步 ORM 查詢,就會拋出 SynchronousOnlyOperation。
最隱蔽的爆點在序列化階段:async view 用 async for 取回 QuerySet 完全合法,但若 Schema 宣告了關聯欄位而查詢時沒有預載,Pydantic 讀取 book.author 的瞬間會觸發延遲查詢,直接在事件迴圈裡引爆例外。
實務上有三條守則:Schema 涉及的關聯一律用 select_related() 或 prefetch_related() 預載;若改用 sync_to_async,就在同步函式內把查詢與序列化整套做完再回傳;沒有外部非同步 I/O 的純資料庫端點,維持同步 def 交給執行緒池,往往比硬上 async 更穩定。
NOTE
五、未來展望:與 Django 官方非同步藍圖的共生
django-ninja 的天花板取決於 Django 本身。它沒有自己的 ORM 或請求週期,Django 底層每往前一步,它就跟著受益。
Django 會不會自己做一個 Ninja?
Django 核心團隊持續推進底層非同步化,但從其重視長期向下相容的穩健風格來看,核心團隊更傾向專注於修補底層基礎設施(如完善 Async ORM、非同步交易與連線池),而非在核心框架中直接捆綁特定的傳輸外層。
這意味著兩者相輔相成:Django 官方對非同步 ORM 的每一點改善,都在替 django-ninja 掃除 SynchronousOnlyOperation 的實務障礙。隨著 Django 底層非同步漸趨成熟,django-ninja 作為現代 API 外層的定位只會更穩固。
總結
架構選型終究是生產力、效能與維護成本的取捨:
- 輕量微服務、即時閘道器或 WebSocket 服務,純 FastAPI 仍是更合適的工具。
- 維護多年的龐大單體且深度依賴 DRF 繼承鏈,維持現狀並以
drf-spectacular補強契約最穩妥。 - 需要成熟的資料庫遷移、開箱即用的管理後台與認證體系,又想要型別提示、Pydantic v2 的序列化效能與乾淨的程式碼,django-ninja 是目前 Python 生態中最平衡的選擇。