作者|瞻新資訊 張安邦執行長(台大醫工背景・十年以上IT服務公司創辦人)
「這支API有做成RESTful的喔」——這句話在提案或技術會議上很常聽到,但很多時候,它其實只是「用HTTP傳JSON」的API,跟真正符合REST設計原則的API,還有一段距離。
這篇文章把REST API的核心概念講清楚:資源導向的設計方式、無狀態性為什麼重要、HTTP方法與狀態碼的語意、冪等性這個容易被忽略卻直接影響系統穩定性的概念,以及為什麼業界公認「多數號稱RESTful的API其實沒有真的做到RESTful」。
REST API是什麼?先破除一個常見誤解
REST(Representational State Transfer,具象狀態傳輸)不是一種協定或技術規格,而是一種架構風格(architectural style)——一套設計網路服務時建議遵循的原則。這個詞最早由Roy Fielding在2000年於加州大學爾灣分校的博士論文中提出,他同時也是HTTP協定規格的共同作者之一,REST的許多設計理念其實就是從HTTP本身的設計精神延伸出來的。
符合REST風格的API稱為「RESTful API」。但實務上,很多人講的「RESTful API」其實只做到了「用HTTP方法+JSON格式」這個最表層的部分,並沒有真的遵守資源導向設計或無狀態性這些更核心的原則——這也是為什麼有些API號稱RESTful,用起來卻感覺不太一致,後面會講到這個現象背後更具體的原因。
資源導向設計:URL要看得出你在動什麼東西
REST風格的核心精神之一,是把系統操作的對象視為「資源」(resource),每一種資源對應一個獨立的網址,再用HTTP方法表達你想對這個資源做什麼動作。這跟早期常見的「動詞式」API設計思維差很多:
- 動詞式(非RESTful):`GET /getUserById?id=1`、`POST /createNewUser`、`POST /deleteUser?id=1`
- 資源導向(RESTful):`GET /users/1`(取得用戶)、`POST /users`(新增用戶)、`DELETE /users/1`(刪除用戶)
資源導向設計的網址本身就在講一個名詞(用戶、訂單、商品),動作則交給HTTP方法表達。這樣設計的好處是,只要看到路由結構,開發者大致就能猜出這支API在操作什麼資源,不需要每支API都額外去記一套獨立的命名規則。

HTTP方法與狀態碼:對應的語意不能亂用
REST API常用的HTTP方法,各自有約定俗成的語意:
| HTTP方法 | 對應動作 | 是否冪等 | 常見成功狀態碼 |
|---|---|---|---|
| GET | 讀取資源 | 是 | 200 OK |
| POST | 新增資源 | 否 | 201 Created |
| PUT | 完整更新資源 | 是 | 200 OK 或 204 No Content |
| PATCH | 部分更新資源 | 否(設計上可做成冪等,但非必然) | 200 OK |
| DELETE | 刪除資源 | 是 | 204 No Content |
狀態碼也有分類語意:2xx代表成功,4xx代表問題出在請求端(例如漏帶必要參數、權限不足),5xx代表問題出在伺服器端。POST建立資源成功後,通常會回傳201 Created,並在回應的Location標頭附上新建立資源的網址,讓客戶端不用另外再查一次。
冪等性:為什麼「重送一次」不該搞出兩筆訂單
上面表格提到的「冪等」(idempotent)是REST設計裡容易被忽略、卻很重要的概念:同一個請求不管送幾次,最後造成的結果都要一樣。
這件事在網路不穩定的情境下特別關鍵。假設使用者按下「刪除訂單」,請求送出去了,但因為網路延遲,畫面遲遲沒有反應,使用者以為沒送成功又點了一次——如果DELETE被設計成冪等,兩次請求的結果都是「這筆訂單不存在」,不會有任何副作用。但如果今天是設計不良、把「新增一筆訂單」的POST動作用在重複點擊會被重送的情境(例如結帳按鈕),沒有額外的防護機制,就可能因為使用者手滑重複點擊、或是客戶端自動重試,而意外建立出兩筆一模一樣的訂單。

這也是為什麼GET、PUT、DELETE在設計上都被期待做成冪等,而POST天生不是冪等的操作——需要額外的機制(例如去重複的請求識別碼)才能避免重試造成的資料異常。
無狀態性:REST為什麼要讓伺服器「健忘」
無狀態性(statelessness)是REST六項架構限制之一,也是常被特別強調的一項:每一個請求都必須包含伺服器理解並完成這個請求所需要的全部資訊,伺服器不會記得上一次的請求內容。
這帶來一個直接的好處:因為伺服器不需要保留任何特定客戶端的上下文(context),任何一台伺服器都能接手處理任何一個請求。這正是水平擴展(horizontal scaling)與負載平衡能夠運作的基礎——如果伺服器需要記住「這個使用者上一步做了什麼」,那使用者的後續請求就必須固定導向同一台伺服器,一旦那台伺服器掛了或流量爆增,就沒辦法簡單地多開一台機器分擔流量。

無狀態性也有取捨:因為不能仰賴伺服器保留的上下文,每個請求勢必要重複帶上一些原本可以省略的資訊,會增加一點網路傳輸的負擔。這是用「稍微多一點的重複資料」換取「系統更容易水平擴展」的設計決策。有個有趣的小知識:Fielding本人認為瀏覽器Cookie其實並不符合REST的無狀態精神,因為Cookie等於是偷偷把狀態塞回了本該無狀態的系統裡,只是這個做法已經在網路世界用得太普遍,很難再改變。
REST的六項架構限制,不是只有無狀態一項
除了無狀態性,REST完整定義了六項架構限制,一個系統要嚴格符合REST風格,理論上都要滿足:
- 統一介面(Uniform Interface):資源的操作方式要一致,不會每個資源各自一套規則,而且客戶端理想上不該寫死每一支API的網址(下一節會展開講這件事)
- 客戶端-伺服器分離(Client-Server):前端與後端的關注點分開,各自可以獨立演進
- 無狀態(Stateless):如前段所述
- 可快取(Cacheable):回應要能明確標示是否可以被快取,以提升效率
- 分層系統(Layered System):客戶端不需要知道自己連的是正式伺服器還是中間的代理層
- 按需程式碼(Code on Demand):唯一非必要的一項,允許伺服器傳送可執行的程式碼給客戶端(例如JavaScript)
實務上,多數業界所說的「RESTful API」並沒有嚴格做到全部六項,比較常見的做法是抓大原則:資源導向的URL設計、正確使用HTTP方法與狀態碼、盡量做到無狀態。這也是為什麼「RESTful」在業界常被當成一個程度問題,而不是有跟沒有的二分法——而其中最常被略過的一項,正是統一介面裡更細緻的規範。
為什麼多數「RESTful API」其實沒有真的做到RESTful?
統一介面這項限制裡,有一個常被略過、卻是Fielding原始定義裡明確要求的概念,叫做HATEOAS(Hypermedia as the Engine of Application State)。簡單說:一個真正符合REST的API,回應內容裡應該附帶「接下來可以做什麼」的連結,讓客戶端動態發現可用的操作,而不是把所有網址都寫死在前端程式碼裡。
舉例來說,查詢一筆訂單時,嚴格RESTful的回應理論上應該附帶「取消這筆訂單」「查看付款狀態」這些後續動作的連結,客戶端看到這些連結才知道現在能做什麼,而不是憑著文件裡記下來的網址規則自己硬組。這樣做的好處是伺服器端要調整網址結構時,不會直接打壞客戶端——因為客戶端本來就不該預先假設網址長什麼樣子。

但實際上,因為動態解析連結會讓客戶端邏輯變複雜,加上業界工具與標準都不夠成熟,多數團隊選擇跳過HATEOAS,改用前後端事先講好、寫死在文件裡的固定網址規則。這也是為什麼技術圈會有「多數所謂的RESTful API其實不算真正RESTful」這種說法的具體原因——不是文字上的吹毛求疵,而是六項限制裡確實有一項在業界被普遍略過。對多數專案來說,這個取捨是合理的:拿HATEOAS的理論完整性,換前後端開發的簡單直覺,通常划算。
REST、GraphQL、SOAP,差在哪裡
除了REST,另外兩種常被拿來比較的API設計方式是GraphQL與SOAP:
REST的架構是每一種資源各自對應一個URL端點,實務上常遇到兩種效率問題:一種是「取得不足」(under-fetching)——要串好幾支API才能湊齊一個畫面需要的資料;另一種是「取得過多」(over-fetching)——明明只需要使用者名稱,卻收到整包使用者資料。GraphQL的設計是用單一端點,由客戶端透過查詢語法指定要拿哪些欄位,一次呼叫就能拿到剛好需要的資料,但也因此在快取與既有工具生態的成熟度上,不如REST來得普及。
SOAP則是更早、更正式嚴謹的協定,通常搭配XML格式與嚴格的WSDL規格文件,常見於對交易正確性要求極高的金融、電信等場域,但相對來說也比REST笨重、開發彈性較低。三者沒有絕對的優劣,要看系統的資料查詢型態、團隊熟悉度、以及對正式規格與彈性的取捨。
API版本控制:URL放版號還是用Header?
API上線之後,功能會持續演進,但既有的使用者不能說改就改,這時候就需要版本控制(versioning),常見做法有三種:
- URL路徑版本(例如`/v1/users`):最常見、對外部串接的第三方最直覺,網址上一眼就看得出版本,瀏覽器直接貼網址也能測試,是多數對外公開API的預設選擇
- Header版本:版本號放在請求標頭裡,網址本身維持乾淨,理論上更貼近REST「網址只代表資源本身」的精神,但測試與除錯相對麻煩一點,比較常見於內部、串接對象單純可控的API
- Query Parameter版本(例如`?version=1`):版本號可以省略、預設用最新版,彈性較高,但嚴謹度介於前兩者之間
不確定該選哪一種時,URL路徑版本是相對安全的預設選項——多數開發者已經習慣這種做法,對外部串接的溝通成本也最低。重點不是哪一種「最RESTful」,而是團隊要說到做到:一旦選定策略,就要維持一致,並清楚溝通每個版本的維護期限。

動手設計REST API之前,先想清楚這幾件事
如果要設計一支新的REST API,建議先想清楚:系統裡有哪些「資源」該獨立成自己的URL、每個資源要開放哪些HTTP方法、哪些操作必須設計成冪等以應付網路重試、要不要嚴格追求無狀態(多數情境建議追求,除非有特殊的效能或相容性考量),以及日後版本演進要用哪一種版本控制策略。想清楚這些,比先把API寫出來、上線後才發現URL命名不一致或狀態碼亂用要有效率得多。
設計完成後,接下來的工作通常是把API規格文件化,方便前後端協作與外部串接——這部分可以參考OpenAPI規範怎麼做。如果評估的是企業要不要導入API串接、串接費用怎麼估,則可以參考API串接的費用與風險這篇文章。
REST API的設計原則看似基礎,但資源導向、無狀態性、冪等性這些細節,直接影響系統未來能不能順利擴展、會不會因為網路重試就搞出資料異常。瞻新資訊在協助客戶做系統開發與API串接時,會依實際情境評估合適的API設計方式,有需要的話歡迎聊聊你的專案需求。
FAQ
REST API是什麼?
REST API是遵循REST(具象狀態傳輸)這種架構風格設計的API,核心概念包括資源導向的URL設計、正確使用HTTP方法與狀態碼、以及無狀態性,讓不同系統之間能透過HTTP協定交換資料。
RESTful跟REST API是同一件事嗎?
是的,「RESTful API」就是指符合REST架構風格設計的API,兩個詞在實務上經常互換使用。
REST一定要無狀態嗎?
無狀態性是REST六項架構限制之一,理論上是必要的。但實務上很多業界所稱的「RESTful API」並沒有嚴格做到全部六項限制,抓大原則(資源導向、正確使用HTTP方法、盡量無狀態)是常見的折衷做法。
為什麼我的API明明有用HTTP方法,卻說不算真正RESTful?
很可能是少了HATEOAS(回應內容附帶後續可用操作的連結,讓客戶端動態發現可用動作,不用把網址寫死)。這是Fielding原始定義裡明確要求、卻因為實作複雜而被業界普遍略過的一項限制,這也是「多數API其實不算真正RESTful」這種說法的具體來源。
什麼是冪等性,為什麼重要?
冪等性是指同一個請求不管送幾次,最後結果都一樣。這對網路不穩定時的請求重試特別重要,設計良好的GET、PUT、DELETE應該做成冪等,避免因為重複送出請求而造成資料異常(例如重複訂單)。
REST跟GraphQL該怎麼選?
REST每種資源各自有URL端點,生態系與快取機制成熟;GraphQL用單一端點讓客戶端指定要拿的資料欄位,能避免取得過多或不足的問題,但工具生態相對沒那麼普及。要看系統的資料查詢型態與團隊熟悉度決定。
API版本控制該用哪一種方式?
不確定的話,URL路徑版本(如`/v1/users`)是相對安全的預設選項,對外部串接最直覺、溝通成本最低。Header版本更貼近REST精神但除錯較麻煩,適合內部可控的API。
關於作者|張安邦執行長
台灣大學醫學工程研究所背景,十年以上軟體開發經歷,領軍瞻新資訊有限公司(2010年成立),完成專案200+、涵蓋40~50+個產業。專長:網頁設計、APP開發、UI/UX設計、LINE Bot自動化、系統開發、資訊安全。


