MUKI AI Summary
在前後端開發中,使用 TypeScript Interface 來定義 API 型別存在一些局限性,如前後端型別不一致和型別驗證不嚴謹等問題。這些問題在專案變得複雜時尤其明顯,因為 API 回應資料來自外部服務,無法保證每次都符合定義的型別。為了解決這些問題,ts-rest 提供了一種自動化的型別同步工具,能夠在前後端之間自動同步 API 型別,並結合 Zod 進行動態驗證,確保資料完整性。
然而,ts-rest 的最佳使用方式是前後端共同導入,這在台灣的產業生態中並不容易實現。即便如此,前端單獨導入 ts-rest 仍能透過 Zod 提供的型別驗證機制,減少錯誤發生的機率,相較於手動建立 TypeScript Interface,ts-rest 能夠提供更強大的型別檢查與資料驗證,特別適合大型或 API 變動頻繁的專案。儘管如此,對於小型或型別驗證需求不高的專案,手動建立 TypeScript Interface 仍然是一個合理的選擇...
在前後端開發的日常裡,呼叫 API 幾乎是無法避免的任務。我通常會用 axios
來發請求,再用 try-catch
處理錯誤。但每次面對不同的 API 回應資料時,手動檢查和處理型別總覺得有些麻煩,尤其是在專案越來越複雜的情況下。你可能也遇過,明明 API 文件說得清清楚楚,但回傳的資料結構卻不如預期,或者回應少了一些關鍵欄位,這種情況通常會導致一些意想不到的錯誤。
使用 TypeScript Interface 碰到的問題
以往我是使用 TypeScript Interface,幫助我們在開發時檢查型別,但我發現光靠 TypeScript 並不足以完全解決這些問題,因為 API 回應資料來自外部服務,不能保證每次回來的資料都符合我們定義的型別。這時候就得自己補上大量的型別檢查邏輯,寫得好累。
先說說我之前使用 TypeScript interface 來定義 API 的狀況。我會定義一個 interface 來描述 API 的請求和回應資料結構,像是這樣:
interface Post { id: number; title: string; content: string; }
當我發送 API 請求時,我會使用這個 interface 來確保請求回來的資料符合預期。表面上看起來都很美好,TypeScript 可以確保我在開發時得到型別提示。
但隨著專案越來越大,我開始碰到幾個問題:
- 前後端型別不一致:當後端對 API 進行調整,改變回應的資料結構時。如果我沒有即時更新前端的 interface,可能會在測試或生產環境才發現型別有誤。
- 型別驗證不嚴謹:雖然 TypeScript interface 能幫助我在開發階段發現一些問題,但如果後端返回了不符合型別的資料,TypeScript 沒辦法在執行時檢查這個錯誤。換句話說,僅靠 interface,資料到前端後依然可能出現不一致。
這些問題會讓整個 API 通訊過程變得脆弱,尤其是當專案變大,或有多個開發團隊時,我得花很多時間檢查並同步前後端的型別定義。後來,我開始思考,有沒有一個更好的方式,能讓前後端的型別同步變得自動化,並且在執行時保證資料的完整性?
直到我發現了 ts-rest 這個酷東西
ts-rest 如何解決這些問題
ts-rest 出現後,改變了我處理前後端型別一致性的方式。它讓我能夠使用一套工具,在前後端之間自動同步 API 的型別,這樣一來,我不需要再手動定義和維護前端的 API 型別了。
我用一個簡單的範例說明 ts-rest 怎麼解決 TypeScript interface 的痛點。
前後端的型別同步
使用 ts-rest 後,後端的型別定義可以自動生成前端的 API 型別,這樣我就不用擔心因為忘記更新 interface 而導致 API 錯誤了。
▼ 後端使用 contract 來定義 api 路由,這邊還有用到 Zod (z.{number, string}
) 來協助驗證資料
const c = initContract(); export const postsContract = c.router({ getPosts: { method: "GET", path: "/posts", responses: { 200: z.array(z.object({ id: z.number(), title: z.string(), content: z.string(), })), }, }, });
透過 ts-rest,這個 API 定義不僅可以在後端使用,前端也能自動拿到型別,確保 API 請求的資料結構一致。而且,在 API 回應時,Zod 會自動驗證資料是否符合定義的結構。
執行時檢查型別
傳統的 TypeScript interface 是靜態檢查,無法在 API 請求的執行階段發現錯誤。而我用 ts-rest 搭配 Zod 後,可以在 API 回應的資料上進行動態驗證,確保即使後端發生變更,或返回不符合預期的資料,我都能在執行時立即捕捉到錯誤。
這樣的好處是,當 API 請求發生異常時,我能夠快速定位並解決問題,而不是等到出錯後再花時間去找原因。
前後端的合作
看下來,也許你會發現一件事情,就是要使用 ts-rest,最好的方式是前後端都要導入 ts-rest 機制,透過後端產生 API 定義讓前端使用。
那麼,問題來了,以台灣的產業生態來看,前後端往往被分成兩個獨立的職業,前端負責使用者介面和互動,後端則負責資料處理和伺服器邏輯。這樣的分工使得「前後端同時導入 ts-rest」變得不太容易。畢竟,後端工程師已經習慣了自己的一套開發流程,要他們改用新的工具或是架構,不僅需要額外的學習成本,還得說服他們這樣的變動能夠帶來足夠的好處。而對於一個前端工程師來說,影響後端技術決策的權限通常有限,可能我們提出了導入 ts-rest 的建議,但後端未必會接受,畢竟對他們來說,可能會覺得這是前端工程師的「需求」。
因此,從我的角度來看,前端想要強迫後端導入 ts-rest 並不現實。這就導致了一個實際的問題:如果後端不使用 ts-rest,前端單獨導入 ts-rest 是否仍然有效?或者,僅僅依賴手動編寫 TypeScript 的 interface,是否會是更好的選擇?
這種情況下,ts-rest 的部分好處,像是更嚴格的型別同步,就無法完全發揮了,但至少在前端,我仍然可以利用 ts-rest 提供的功能來建立自己的 API 合約,透過 Zod 進行型別驗證,這樣即使後端不使用 ts-rest,我依然可以確保從 API 回應中獲得的資料是符合預期的。相對來說,手動建立 TypeScript interface 雖然也能做到基本的型別檢查,但缺乏了像 ts-rest 那樣的自動化優勢和嚴謹的檢查機制,也讓我需要自己負責更多的檢查和維護工作。
我也整理了一張表,比較前端使用 ts-rest,以及手動建立 Typescript Interface 的差別,供大家參考
前端導入 ts-rest vs. 手動建立 TypeScript Interface
比較項目 | 前端導入 ts-rest | 手動建立 TypeScript interface |
---|---|---|
型別同步 | - 需要手動撰寫 API 的定義,無法自動與後端同步 - 因為後端沒有使用 ts-rest,必須自行確認 API 文件與後端保持一致 | - 完全手動撰寫和維護型別 - 容易出現前後端型別不一致的情況,特別是當 API 變更時 |
開發體驗 | - 使用 ts-rest 提供的 API 工具可以減少手寫請求邏輯,並與 TypeScript 內建的型別系統結合 - 可以定義 API 的各種操作方法 | - 必須手動處理所有 API 請求邏輯 - TypeScript 的型別僅提供靜態檢查,無法處理動態資料的驗證 |
資料驗證 (runtime) | - 結合 Zod,可以對 API 回應進行型別檢查,保證資料符合預期 - 額外的 Zod 驗證可以補充 TypeScript 無法檢查動態資料的限制 | - 無內建的資料驗證功能,必須手動撰寫驗證邏輯 - TypeScript 僅在編譯階段檢查,無法在執行階段進行驗證,遇到型別錯誤時需手動排查 |
代碼維護性 | - 一旦定義好 API,ts-rest 可以保持較高的一致性 - 若後端 API 改變,依然需要手動修改 API 型別定義 | - 當後端 API 發生變更時,需要手動更新 API 型別 |
學習曲線 | - 需要額外學習 ts-rest 的語法與整合方式 - 需了解 Zod 的驗證工具來搭配使用 | - 熟悉 TypeScript interface 就能直接上手 - 無需額外學習框架或工具,但需要撰寫更多自訂邏輯 |
適用場景 | - 適合需要更強型別驗證和自動化工具的專案 - 有 API 變動頻繁,且需要保證資料安全性的場景 | - 適合小型專案或 API 相對穩定的場景 - 對於型別驗證需求不高的情況,手動建立 interface 也足夠 |
維護成本與程式碼量 | - 必須撰寫 ts-rest API 定義與 Zod 驗證規則,初期設定會增加程式碼的負擔 - 減少後期維護成本,長期來看程式碼負擔不會過大 | - 每個 API 需要單獨定義 interface 和處理驗證邏輯 - 雖然初期程式碼負擔小,但隨著 API 增加,維護成本會上升 |
小結
- 若專案需求:
- 需要強大的型別檢查與資料驗證,且 API 請求邏輯複雜,可以考慮使用 ts-rest,即使後端沒有使用,前端也能享受 Zod 結合 TypeScript 的驗證優勢。
- API 變更頻繁,或項目規模較大時,ts-rest 能夠減少你手動維護型別和驗證的工作量。
- 若專案需求:
- 小型專案或 API 較穩定,且型別驗證需求不高,則可以繼續使用手動建立 TypeScript interface,因為這樣更簡單,也不需要額外學習新工具。
總而言之,即使後端不使用 ts-rest,我在前端仍然可以受益於它的型別驗證機制,尤其是配合 Zod 使用時,能夠顯著減少錯誤的發生。但若後端也能導入 ts-rest,這會使整個 API 管理流程更加高效,型別也能夠自動同步,進而達到全端一致的型別保障。不過使用 Typescript interface 依然是一些簡單或小型專案的合理選擇。
結論
我一開始在玩 ts-rest 時,也是只從前端導入 ts-rest,但光這樣,就發現了一些有趣且實用的功能。
接下來,我想透過一系列的文章,深入介紹 ts-rest 的各種應用,從如何與 Zod 結合進行型別驗證,到如何在前後端協作中發揮它的最大效益。希望這些分享能讓你對 ts-rest 有更全面的了解,並在你的開發過程中找到適合的使用場景。