前言

在寫 API 的時候,很容易把成功流程寫得很完整,但錯誤流程只簡單回一句「失敗」。

例如:

1
2
3
4
{
"success": false,
"message": "error"
}

這種回應對使用者不友善,對前端不友善,對未來要查問題的自己也不友善。

這篇整理一下,我覺得 API 錯誤處理至少可以注意的幾件事。

錯誤訊息要能被理解

錯誤訊息的第一個目的,是讓呼叫端知道發生了什麼事。

例如登入失敗時,如果只回:

1
2
3
{
"message": "failed"
}

前端很難判斷要顯示什麼,也不知道是帳號不存在、密碼錯誤,還是系統暫時異常。

可以改成比較明確的格式:

1
2
3
4
{
"code": "INVALID_CREDENTIALS",
"message": "帳號或密碼錯誤"
}

message 給人看,code 給程式判斷。

這樣前端可以根據 code 做不同處理,也不用去解析文字內容。

HTTP Status Code 要用對

API 回應不只看 body,也要看 HTTP status code。

常見可以這樣使用:

狀態碼 使用情境
400 請求格式錯誤或參數不合法
401 未登入或 token 無效
403 已登入,但沒有權限
404 找不到資源
409 狀態衝突,例如資料已存在
500 伺服器未預期錯誤

不要所有錯誤都回 200 OK,然後在 body 裡寫 success: false

這樣雖然前端也能處理,但會讓 API 語意變得不清楚,也不利於監控工具判斷錯誤。

錯誤格式最好一致

如果每支 API 的錯誤格式都不一樣,前端處理起來會很痛苦。

例如有些 API 回:

1
2
3
{
"error": "not found"
}

有些回:

1
2
3
4
{
"success": false,
"message": "not found"
}

有些又回:

1
2
3
4
5
{
"errors": [
"name is required"
]
}

這會讓呼叫端需要為每種格式寫不同的解析邏輯。

比較好的做法是先定義統一格式,例如:

1
2
3
4
5
6
7
8
9
10
{
"code": "VALIDATION_FAILED",
"message": "欄位驗證失敗",
"details": [
{
"field": "email",
"message": "Email 不可為空"
}
]
}

不一定每個欄位都要出現,但整體結構最好一致。

不要把敏感資訊丟給前端

錯誤訊息要清楚,但不能把內部細節全部暴露出去。

例如正式環境不應該直接回:

1
SQL syntax error near 'users.password_hash'

或:

1
Object reference not set to an instance of an object at UserService.cs:line 87

這些資訊對使用者沒有幫助,還可能暴露系統結構。

比較好的做法是:

  • 前端拿到簡潔、可理解的錯誤訊息。
  • 後端 log 留下完整 exception 與 trace id。
  • 必要時回傳 trace id,方便客服或工程師追查。

例如:

1
2
3
4
5
{
"code": "INTERNAL_SERVER_ERROR",
"message": "系統暫時無法處理,請稍後再試",
"traceId": "0HN3K2ABC1234"
}

使用者看到的是可理解的訊息,工程師也能靠 trace id 查 log。

結語

API 錯誤處理不是把 exception 接住就結束了。

一個好的錯誤回應,應該要做到:

  • status code 語意正確。
  • 錯誤格式一致。
  • message 給人看,code 給程式判斷。
  • 不暴露敏感內部細節。
  • 後端保留足夠 log 方便追查。

錯誤流程通常不是最開心的部分,但它會直接影響系統的可維護性與使用體驗。