Post
2025年10月21日 下午12:11

在開發中後期遇到 multipart/form-data 的問題時,在評估後我們選擇改用 TUS (The Upload Server)。TUS 是一個開放的、獨立於 GraphQL 的協定,專為可靠的、可恢復的檔案上傳而設計。它的核心理念,就是將大型檔案拆分成可管理的區塊,並提供斷點續傳的能力。

TUS 在 GraphQL 架構中的角色並非直接融入 GraphQL 的請求處理,而是作為一個外部的、專門的檔案上傳服務,而 GraphQL API 則扮演協調者 (Coordinator) 的角色,尤其在檔案完成上傳後處理業務邏輯方面。

第一步:Client 與 TUS 伺服器互動 (HTTP POST / PATCH / HEAD)

客戶端: 當使用者準備上傳檔案時,Client 會使用 TUS library(例如 tus-js-client)與 TUS 伺服器進行互動。這個過程會由 TUS library 自動處理:

  • Client 會先向 tusd 送出一個 HTTP POST request,初始化一個新的上傳資源。
  • tusd 會回應一個唯一的、用於該檔案上傳的 TUS 上傳 URL (例如 https://tus-server.example.com/uploads/your-upload-id)。
  • 接著,Client 會將檔案分割成多個資料區塊 (chunks),並透過一系列的 HTTP PATCH 請求將這些資料區塊逐一上傳到這個 TUS 上傳 URL。
  • 過程中,TUS client 會檢查 Upload-Offset 確保從正確的位置續傳。
  • 若上傳中斷,client 會利用 HEAD 請求查詢當前進度,然後從上次中斷的地方恢復上傳。

tusd: 在此互動過程中,tusd 會獨立接收並組裝檔案資料,同時維護上傳 session 的狀態。

第二步:GraphQL 接收上傳完成通知並執行後續邏輯 (GraphQL Mutation)

客戶端: 當 TUS client 確認檔案已成功上傳到 TUS 伺服器後,client 會再次向 GraphQL API 發送一個 Mutation request。這個 Mutation 會攜帶 TUS 服務為該檔案產生的唯一識別符 fileId,以及任何需要與該檔案關聯的 metadata。

GraphQL API (後端): 接收到這個完成通知後,會執行一系列的處理:

  • 它會根據客戶端提供的 fileId 驗證檔案是否確實已成功上傳並存在。
  • 接著,它會執行與該檔案相關的後續業務邏輯,例如:將檔案關聯到使用者帳戶,產生縮圖,更新資料庫紀錄,或觸發其他後端處理任務。
  • 最終,GraphQL API 會回傳操作是否成功的結果。

TUS 架構的顯著優勢:

  • 徹底解決斷點續傳問題: TUS 從協定層面支援斷點續傳,即使面對不穩定的網路或大型檔案,也能提供無縫、可靠的上傳體驗,讓使用者可以從上次中斷的地方繼續上傳。

  • 提升 Proxy 效能與可擴充性: 當客戶端直接與 TUS 伺服器互動時,大型檔案的上傳流量不再經過主 GraphQL API 的代理,這能大幅減輕反向代理 (Reverse Proxy) 和 API Gateway 的負擔,避免因緩衝大型檔案而承受壓力。同時,TUS 伺服器可以獨立於 GraphQL 服務進行擴充,若檔案上傳需求大增,可專門擴充 TUS 服務叢集而不影響 GraphQL 服務效能。此外,TUS 服務可部署在更靠近使用者端點的地區,或利用專門為檔案上傳優化的基礎設施,從而提供更快的上傳速度。

  • 降低 GraphQL 服務壓力與職責分離: GraphQL 服務能專注於其核心職責——處理資料的查詢與變更。繁重的檔案 I/O 操作則交由專門的 TUS 服務高效處理,這種職責的分離使得 GraphQL 服務更加輕量、高效且更容易維護。

  • 精細的進度回報: 由於 TUS 採用分塊上傳機制,TUS 客戶端函式庫能提供精確的即時上傳進度資訊,大幅改善了大型檔案上傳過程中的使用者體驗。

TUS 架構的缺點:

  • 最後的 GraphQL Mutation 無法被納入進度顯示:雖然這是 GraphQL 造成的,實際上也不算是太大的缺陷,但使用 TUS 上傳時的最後一步看起來會像是上傳卡住了。
  • 要多一組 tusd 很不方便:若非使用 containerize 或類似的部署方式,tusd 本身的位置對雲端環境是稍微有點麻煩的,也需要擔心 network throttle 的問題。
Related Comments

雖然不是重點但這篇文讓我發現Mosir的斷行是break-all XD 好在意

用過 Go + gqlgen 和 Node.js + TypeORM,反而覺得 GraphQL 被捧的太高了。沒有解決問題,反而製造問題,例如:N+1 需要 Dataloader 處理、無法傳遞二進制…

Facebook 當初設計 GraphQL,是為了讓 Resolver 能從不同微服務整合資料,統一回傳給前端。但很多人把它當成 HTTP API 的替代方案使用,就像 JWT 被誤用為取代 Session 一樣。

開發速度遠不如一個 POST /upload_video API。但前端生態彷彿不用上新技術就會被淘汰。似乎懂得什麼該捨棄,比學會技術還難。

但我現在用 Go + HTMX 超快樂… 🫠