跳至正文

📬 HTTP 方法

📖 定義

HTTP 方法是客戶端向伺服器表達想執行的動作。每個方法都有特定的意義和用途,是 RESTful API 設計的核心。

🎯 用比喻來理解

圖書館系統

GET    = 查找書籍 (查詢)
├─ 詢問圖書管理員"這本書有嗎?"
├─ 僅取得資料
└─ 圖書館狀態不變

POST = 登記新書 (建立)
├─ 新增書籍到圖書館
├─ 圖書館狀態改變
└─ 建立新資源

PUT = 修改書籍資訊 (全面更新)
├─ 重新寫入書籍的所有資訊
└─ 全部替換

PATCH = 部分修改書籍資訊 (部分更新)
├─ 僅修改部分書籍資訊 (例如:借閱狀態)
└─ 僅變更部分內容

DELETE = 丟棄書籍 (刪除)
├─ 從圖書館移除書籍
└─ 刪除資源

💡 主要 HTTP 方法

GET - 查詢資料

用途: 從伺服器獲取資源

特點:
├─ 安全(Safe):不改變伺服器狀態
├─ 冪等(Idempotent):多次呼叫結果相同
├─ 可快取
└─ 出現在瀏覽器歷史中

請求範例:
GET /api/users HTTP/1.1
Host: example.com
// JavaScript fetch API
fetch('https://api.example.com/users')
.then(response => response.json())
.then(data => console.log(data));

// jQuery
$.get('https://api.example.com/users', function(data) {
console.log(data);
});

// axios
axios.get('https://api.example.com/users')
.then(response => console.log(response.data));
# curl
curl https://api.example.com/users

# 查詢特定使用者
curl https://api.example.com/users/123

# 使用查詢參數
curl "https://api.example.com/users?page=1&limit=10"

回應範例:

HTTP/1.1 200 OK
Content-Type: application/json

{
"users": [
{ "id": 1, "name": "王小明" },
{ "id": 2, "name": "李大同" }
]
}

POST - 建立資料

用途: 在伺服器建立新資源

特點:
├─ 非安全:改變伺服器狀態
├─ 非冪等:多次呼叫會建立多個資源
├─ 不可快取
└─ 請求主體包含資料

請求範例:
POST /api/users HTTP/1.1
Host: example.com
Content-Type: application/json

{
"name": "王小明",
"email": "wang@example.com"
}
// fetch API
fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: '王小明',
email: 'wang@example.com'
})
})
.then(response => response.json())
.then(data => console.log(data));

// axios
axios.post('https://api.example.com/users', {
name: '王小明',
email: 'wang@example.com'
})
.then(response => console.log(response.data));
# curl
curl -X POST https://api.example.com/users \
-H "Content-Type: application/json" \
-d '{"name":"王小明","email":"wang@example.com"}'

回應範例:

HTTP/1.1 201 Created
Location: /api/users/123
Content-Type: application/json

{
"id": 123,
"name": "王小明",
"email": "wang@example.com",
"createdAt": "2025-01-26T10:00:00Z"
}

PUT - 全面更新資料

用途: 用新資料完全替換資源

特點:
├─ 非安全:改變伺服器狀態
├─ 冪等:多次呼叫結果相同
├─ 完全替換資源
└─ 若不存在可能會建立

請求範例:
PUT /api/users/123 HTTP/1.1
Host: example.com
Content-Type: application/json

{
"name": "王小明",
"email": "newemail@example.com",
"phone": "0912-345-678"
}
// fetch API
fetch('https://api.example.com/users/123', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: '王小明',
email: 'newemail@example.com',
phone: '0912-345-678'
})
})
.then(response => response.json())
.then(data => console.log(data));

// axios
axios.put('https://api.example.com/users/123', {
name: '王小明',
email: 'newemail@example.com',
phone: '0912-345-678'
})
.then(response => console.log(response.data));
# curl
curl -X PUT https://api.example.com/users/123 \
-H "Content-Type: application/json" \
-d '{"name":"王小明","email":"newemail@example.com","phone":"0912-345-678"}'

PATCH - 部分更新資料

用途: 僅修改資源的部分內容

特點:
├─ 非安全:改變伺服器狀態
├─ 可能是冪等,也可能不是
├─ 僅修改部分欄位
└─ 比 PUT 更有效率

請求範例:
PATCH /api/users/123 HTTP/1.1
Host: example.com
Content-Type: application/json

{
"email": "newemail@example.com"
}
// fetch API
fetch('https://api.example.com/users/123', {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: 'newemail@example.com'
})
})
.then(response => response.json())
.then(data => console.log(data));

// axios
axios.patch('https://api.example.com/users/123', {
email: 'newemail@example.com'
})
.then(response => console.log(response.data));
# curl
curl -X PATCH https://api.example.com/users/123 \
-H "Content-Type: application/json" \
-d '{"email":"newemail@example.com"}'

PUT vs PATCH 比較:

原始資料:
{
"id": 123,
"name": "王小明",
"email": "old@example.com",
"phone": "0912-1111-2222"
}

PUT 請求 (全面替換):
{
"name": "王小明",
"email": "new@example.com"
}
結果:
{
"id": 123,
"name": "王小明",
"email": "new@example.com"
// phone 欄位消失!
}

PATCH 請求 (部分更新):
{
"email": "new@example.com"
}
結果:
{
"id": 123,
"name": "王小明",
"email": "new@example.com",
"phone": "0912-1111-2222"
// phone 欄位保留!
}

DELETE - 刪除資料

用途: 刪除資源

特點:
├─ 非安全:改變伺服器狀態
├─ 冪等:多次刪除結果相同
├─ 回應主體可能為空
└─ 可能無法復原

請求範例:
DELETE /api/users/123 HTTP/1.1
Host: example.com
// fetch API
fetch('https://api.example.com/users/123', {
method: 'DELETE'
})
.then(response => {
if (response.ok) {
console.log('刪除完成');
}
});

// axios
axios.delete('https://api.example.com/users/123')
.then(response => console.log('刪除完成'));
# curl
curl -X DELETE https://api.example.com/users/123

回應範例:

HTTP/1.1 204 No Content

HTTP/1.1 200 OK
Content-Type: application/json

{
"message": "使用者已刪除",
"deletedId": 123
}

🔍 其他 HTTP 方法

HEAD - 僅查詢標頭

用途:與 GET 相同,但僅返回標頭,不返回主體

使用場景:
├─ 確認資源是否存在
├─ 確認檔案大小
└─ 確認修改時間

範例:
HEAD /api/users/123 HTTP/1.1
Host: example.com

回應:
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 256
Last-Modified: Sat, 26 Jan 2025 10:00:00 GMT
# curl
curl -I https://api.example.com/users/123

OPTIONS - 確認支援的方法

用途:確認伺服器支援的方法(CORS 預檢請求)

範例:
OPTIONS /api/users HTTP/1.1
Host: example.com

回應:
HTTP/1.1 200 OK
Allow: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Origin: *
# curl
curl -X OPTIONS https://api.example.com/users -i

📊 方法屬性比較

┌─────────┬──────┬────────┬─────────┬────────────┐
│ 方法 │ 安全 │ 冪等性 │ 可快取 │ 請求主體 │
├─────────┼──────┼────────┼─────────┼────────────┤
│ GET │ ✅ │ ✅ │ ✅ │ ❌ │
│ POST │ ❌ │ ❌ │ ❌ │ ✅ │
│ PUT │ ❌ │ ✅ │ ❌ │ ✅ │
│ PATCH │ ❌ │ △ │ ❌ │ ✅ │
│ DELETE │ ❌ │ ✅ │ ❌ │ △ │
│ HEAD │ ✅ │ ✅ │ ✅ │ ❌ │
│ OPTIONS │ ✅ │ ✅ │ ❌ │ ❌ │
└─────────┴──────┴────────┴─────────┴────────────┘

術語解釋:
- 安全(Safe):不改變伺服器狀態
- 冪等(Idempotent):多次執行結果相同
- 可快取:可以快取回應

💡 RESTful API 設計

CRUD 與 HTTP 方法映射

Create  → POST
Read → GET
Update → PUT / PATCH
Delete → DELETE

RESTful API 範例

資源:使用者(users)

GET /users - 查詢所有使用者
GET /users/123 - 查詢特定使用者
POST /users - 建立新使用者
PUT /users/123 - 全面更新使用者
PATCH /users/123 - 部分更新使用者
DELETE /users/123 - 刪除使用者

巢狀資源:
GET /users/123/posts - 特定使用者的貼文列表
GET /users/123/posts/456 - 查詢特定貼文
POST /users/123/posts - 建立新貼文
PUT /users/123/posts/456 - 更新貼文
DELETE /users/123/posts/456 - 刪除貼文

實戰範例:部落格 API

// 部落格貼文 API

// 1. 查詢貼文列表(分頁、篩選)
GET /api/posts?page=1&limit=10&category=tech

// 2. 查詢特定貼文
GET /api/posts/123

// 3. 建立新貼文
POST /api/posts
{
"title": "完全掌握 HTTP 方法",
"content": "...",
"category": "tech"
}

// 4. 全面更新貼文
PUT /api/posts/123
{
"title": "完全掌握 HTTP 方法(已修改)",
"content": "...",
"category": "tech"
}

// 5. 部分更新貼文(增加瀏覽次數)
PATCH /api/posts/123
{
"views": 101
}

// 6. 刪除貼文
DELETE /api/posts/123

// 7. 留言相關
GET /api/posts/123/comments - 留言列表
POST /api/posts/123/comments - 建立留言
DELETE /api/posts/123/comments/456 - 刪除留言

🤔 常見問題

Q1. POST 與 PUT 有何不同?

答:

POST:
├─ 建立新資源
├─ 伺服器決定資源 URI
├─ 非冪等(多次呼叫會建立多個資源)
└─ 例如:POST /users

PUT:
├─ 完全替換資源
├─ 客戶端指定資源 URI
├─ 冪等(多次呼叫結果相同)
└─ 例如:PUT /users/123

實戰比喻:
POST = "請幫我開新帳戶"(銀行分配帳號)
PUT = "請將 123 號帳戶資訊改成這樣"

Q2. 可以在 GET 請求中傳送主體嗎?

答:

技術上可以,但:
├─ HTTP 規範允許,但不建議
├─ 許多伺服器/框架會忽略
├─ 快取、記錄等可能有非預期行為
└─ 建議使用查詢參數

❌ 不好的例子:
GET /api/users
{
"filters": { "age": 25 }
}

✅ 好的例子:
GET /api/users?age=25

或複雜搜尋使用 POST:
POST /api/users/search
{
"filters": { "age": 25, "city": "台北" }
}

Q3. 可以在 DELETE 請求中傳送主體嗎?

答:

可以,但要小心:
├─ HTTP 規範允許
├─ 部分伺服器/代理可能會忽略
├─ 通常在 URI 包含資源 ID
└─ 主體用於傳遞額外資訊

使用範例:

✅ 一般方法:
DELETE /api/users/123

✅ 複雜刪除條件:
DELETE /api/posts/bulk
{
"ids": [1, 2, 3, 4, 5]
}

✅ 傳遞刪除原因:
DELETE /api/users/123
{
"reason": "使用者要求",
"confirm": true
}

Q4. 為什麼冪等性很重要?

答:

冪等性的重要性:

1. 應對網路故障
├─ 請求失敗時可重試
├─ 無需擔心重複請求
└─ 安全重新處理

範例:
GET /users/123 → 多次查詢安全
POST /users → 多次呼叫會建立多個使用者!
PUT /users/123 → 多次呼叫結果相同
DELETE /users/123 → 多次刪除結果相同

2. 客戶端重試邏輯
├─ 逾時時可安全重試
└─ 可實作自動重試機制

3. 快取與最佳化
├─ 冪等請求可快取
└─ 便於效能最佳化

Q5. 實務上如何選擇方法?

答:

選擇指南:

1. 僅查詢資料
→ GET

2. 建立新資料
→ POST

3. 完全替換資料
→ PUT
例:完整更新使用者資料

4. 部分修改資料
→ PATCH
例:增加按讚數、僅變更狀態

5. 刪除資料
→ DELETE

6. 複雜查詢/搜尋
→ POST /search
(避開 GET 的 URL 長度限制)

實戰範例:

// ✅ 好的例子
GET /api/products - 產品列表
GET /api/products/123 - 產品詳情
POST /api/products - 建立產品
PATCH /api/products/123/stock - 僅修改庫存
PUT /api/products/123 - 完整更新產品資訊
DELETE /api/products/123 - 刪除產品

// ❌ 不好的例子
GET /api/deleteProduct?id=123 - 刪除應使用 DELETE
POST /api/getProducts - 查詢應使用 GET
POST /api/updateProduct - 更新應使用 PUT/PATCH

🎓 實作練習

1. 使用 fetch API 練習

// GET 請求
async function getUser(id) {
const response = await fetch(`https://api.example.com/users/${id}`);
const data = await response.json();
return data;
}

// POST 請求
async function createUser(userData) {
const response = await fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(userData)
});
return await response.json();
}

// PUT 請求
async function updateUser(id, userData) {
const response = await fetch(`https://api.example.com/users/${id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(userData)
});
return await response.json();
}

// PATCH 請求
async function patchUser(id, partialData) {
const response = await fetch(`https://api.example.com/users/${id}`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(partialData)
});
return await response.json();
}

// DELETE 請求
async function deleteUser(id) {
const response = await fetch(`https://api.example.com/users/${id}`, {
method: 'DELETE'
});
return response.ok;
}

2. 包含錯誤處理

async function apiRequest(url, options = {}) {
try {
const response = await fetch(url, options);

if (!response.ok) {
throw new Error(`HTTP 錯誤!狀態:${response.status}`);
}

return await response.json();
} catch (error) {
console.error('API 請求失敗:', error);
throw error;
}
}

// 使用範例
try {
const user = await apiRequest('https://api.example.com/users/123');
console.log(user);
} catch (error) {
console.error('使用者查詢失敗');
}

🔗 相關文檔

🎬 結語

HTTP 方法是 RESTful API 的核心。理解每個方法的特性並適當使用,可以設計出直觀且易於維護的 API!

下一步:閱讀 HTTP 狀態碼,了解 200、404、500 等狀態碼。