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 + DRFDjango + 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 的輕快。但只要系統涉及營運、客服、財務與權限控管,開發者很快會被捲入基礎設施的建置成本中:

  1. Django Admin 的生產力槓桿:在 Django 裡定義好 Model 後,只要一行 admin.site.register,就能直接獲得具備分頁、篩選、搜尋與權限的後台。FastAPI 即使外掛 SQLAdmin,其成熟度與擴充彈性依然存在明顯差距,團隊常得花費數週重造輪子。
  2. 資料庫遷移(Migrations)的可靠度:Django 的 makemigrations 與 migrate 是業界最成熟的關聯遷移工具。SQLAlchemy 搭配 Alembic 在處理欄位重新命名時,受限於工具的自動偵測限制,常需手寫 Python 遷移腳本,失誤風險較高。
  3. 開箱即用的認證與安全預設: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

程式碼簡化背後反映了四項工程躍升:

  1. 宣告式自動前置驗證:DRF 需要在 Serializer 寫 validate_<field> 並在 View 裡手動檢查 serializer.is_valid();django-ninja 以 Pydantic Schema 作為資料契約與驗證規範的單一真實來源(Single Source of Truth),不符規格的請求在進 View 之前就會被擋下並回傳標準 422 錯誤。
  2. 自動型別傳遞與重構安全:BookIn 與 BookOut 是標準 Python 類別,IDE 能清楚解析型別;DRF 在 fields = ['title', 'price'] 使用字串清單,重構時無法獲得靜態分析工具的保護。
  3. 宣告式路由取代繁瑣設定:DRF 在 APIView 下需手動維護 path,在 ViewSet 下又得依賴 DefaultRouter 的約定命名;裝飾器路由讓端點路徑、輸入規格與回應型別集中在同一處。
  4. 嚴謹的自動化 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 的相容工作則由社群貢獻者分擔。

版本里程碑

相容範圍與維護節奏

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 為大型專案提供了結構化的物件導向增強層:

對於習慣 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

遷移實務可依循三個步驟:

  1. 路由層分流(URL Routing):在根目錄 urls.py 中,將歷史端點維持於 path("api/v1/", include(drf_router.urls)),將新端點掛載至 path("api/v2/", ninja_api.urls),底層存取相同的 Django Models。
  2. 認證層打通(Shared Authentication):撰寫輕量的 Ninja HttpBearer 轉接類別,內部直接沿用 DRF SimpleJWT 的驗證後端(如 UntypedToken),讓舊 Token 能透過新端點驗證。針對 Bearer Token 請求自動豁免 CSRF,Cookie 請求則維持防護。
  3. 策略性試水溫:優先挑選「純資料查詢、高頻存取、回傳大量資料」的報表或商品清單端點。這類端點在 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 更穩定。


五、未來展望:與 Django 官方非同步藍圖的共生

django-ninja 的天花板取決於 Django 本身。它沒有自己的 ORM 或請求週期,Django 底層每往前一步,它就跟著受益。

Django 會不會自己做一個 Ninja?

Django 核心團隊持續推進底層非同步化,但從其重視長期向下相容的穩健風格來看,核心團隊更傾向專注於修補底層基礎設施(如完善 Async ORM、非同步交易與連線池),而非在核心框架中直接捆綁特定的傳輸外層。

這意味著兩者相輔相成:Django 官方對非同步 ORM 的每一點改善,都在替 django-ninja 掃除 SynchronousOnlyOperation 的實務障礙。隨著 Django 底層非同步漸趨成熟,django-ninja 作為現代 API 外層的定位只會更穩固。

總結

架構選型終究是生產力、效能與維護成本的取捨: