Golang 網路編程:RESTful API 開發
簡介
在現代的分散式系統與微服務架構中,RESTful API 已成為前後端溝通的事實標準。無論是行動 App、單頁應用(SPA)或是其他服務之間的資料交換,都離不開一套設計良好的 HTTP 介面。Go(Golang)以其高效能、原生支援並發以及簡潔的語法,成為開發高可用 RESTful 服務的首選語言之一。
本篇文章將帶領 初學者到中級開發者,從基礎概念到實作細節,完整掌握在 Go 中建構 RESTful API 的核心技巧。透過實際範例,我們會說明如何使用標準函式庫 net/http、流行的路由框架(如 gorilla/mux)、JSON 序列化、錯誤處理與測試,讓你能快速上手、部署到生產環境。
核心概念
1. HTTP 基礎與 REST 原則
- 資源 (Resource):每個資源以唯一的 URI 表示,例如
/users/123代表 ID 為 123 的使用者。 - 動作 (Verb):使用 HTTP 方法(GET、POST、PUT、PATCH、DELETE)對資源執行 CRUD 操作。
- 狀態碼 (Status Code):根據請求結果回傳 2xx、4xx、5xx 等標準碼,讓客戶端能正確判斷結果。
- 無狀態 (Stateless):每一次請求必須攜帶足夠資訊,伺服器不應保留客戶端的會話狀態。
Tip:遵循這些原則能讓 API 更易於理解、測試與維護。
2. Go 的 net/http 標準庫
Go 內建的 net/http 已足以處理大多數 RESTful 需求:
package main
import (
"encoding/json"
"log"
"net/http"
)
type Message struct {
Text string `json:"text"`
}
// 只要實作 `http.Handler` 介面,即可作為處理函式
func helloHandler(w http.ResponseWriter, r *http.Request) {
// 設定回傳的 Content-Type
w.Header().Set("Content-Type", "application/json")
// 產生回應資料
resp := Message{Text: "Hello, Golang RESTful API!"}
// 使用 json.NewEncoder 直接寫入 ResponseWriter
json.NewEncoder(w).Encode(resp)
}
func main() {
http.HandleFunc("/hello", helloHandler)
log.Println("Server listening on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
http.HandleFunc為最簡單的路由註冊方式。json.NewEncoder(w).Encode同時完成序列化與寫入,避免手動Marshal再Write的繁瑣。
3. 使用路由框架:gorilla/mux
對於較複雜的路由需求(路徑參數、正則表達式、子路由),gorilla/mux 是最常見的選擇:
package main
import (
"encoding/json"
"log"
"net/http"
"github.com/gorilla/mux"
)
type User struct {
ID string `json:"id"`
Name string `json:"name"`
}
// 取得單一使用者
func getUserHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r) // 取得路徑參數
userID := vars["id"]
// 這裡僅示範回傳固定資料,實務上會從 DB 讀取
user := User{ID: userID, Name: "Alice"}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(user)
}
func main() {
r := mux.NewRouter()
// 支援路徑參數 {id}
r.HandleFunc("/users/{id}", getUserHandler).Methods(http.MethodGet)
log.Println("Server listening on :8080")
log.Fatal(http.ListenAndServe(":8080", r))
}
mux.Vars(r)可直接取得 URL 中的參數。.Methods(http.MethodGet)限制只接受 GET 請求,提升安全性。
4. 請求與回應的結構化
在 RESTful API 中,統一的錯誤回應格式 能讓前端快速解析:
type APIError struct {
Code int `json:"code"` // HTTP 狀態碼
Message string `json:"message"` // 錯誤訊息
}
// 產生錯誤回應的工具函式
func writeError(w http.ResponseWriter, status int, msg string) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
json.NewEncoder(w).Encode(APIError{Code: status, Message: msg})
}
使用方式:
if r.Method != http.MethodPost {
writeError(w, http.StatusMethodNotAllowed, "Only POST is allowed")
return
}
5. 中介層(Middleware)與跨域設定(CORS)
許多實務需求需要在每個請求前後執行共通邏輯,例如 日誌、驗證、CORS。以下示範一個簡易的 CORS 中介層:
func corsMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
// 若是預檢請求直接回 200
if r.Method == http.MethodOptions {
w.WriteHeader(http.StatusOK)
return
}
next.ServeHTTP(w, r)
})
}
在 main 中掛載:
r.Use(corsMiddleware) // 針對所有路由套用
6. 資料庫操作(以 GORM 為例)
實務 API 必須與資料庫互動。以下示範使用 GORM(支援 MySQL、PostgreSQL 等)完成 CRUD:
package main
import (
"encoding/json"
"log"
"net/http"
"github.com/gorilla/mux"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type Product struct {
ID uint `json:"id" gorm:"primaryKey"`
Name string `json:"name"`
Price int `json:"price"`
}
var db *gorm.DB
func initDB() {
dsn := "user:password@tcp(127.0.0.1:3306)/testdb?charset=utf8mb4&parseTime=True&loc=Local"
var err error
db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
log.Fatalf("failed to connect database: %v", err)
}
// 自動遷移(建立表格)
db.AutoMigrate(&Product{})
}
// 取得全部商品
func listProducts(w http.ResponseWriter, r *http.Request) {
var products []Product
db.Find(&products)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(products)
}
// 新增商品
func createProduct(w http.ResponseWriter, r *http.Request) {
var p Product
if err := json.NewDecoder(r.Body).Decode(&p); err != nil {
writeError(w, http.StatusBadRequest, "Invalid JSON")
return
}
db.Create(&p)
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(p)
}
func main() {
initDB()
r := mux.NewRouter()
r.HandleFunc("/products", listProducts).Methods(http.MethodGet)
r.HandleFunc("/products", createProduct).Methods(http.MethodPost)
log.Println("API server listening on :8080")
log.Fatal(http.ListenAndServe(":8080", r))
}
db.AutoMigrate可自動根據結構體建立或更新資料表。json.NewDecoder(r.Body).Decode直接把請求 body 轉成結構體,減少手動驗證的錯誤。
7. 單元測試與整合測試
Go 的測試框架內建於 testing 套件,配合 httptest 可輕鬆測試 HTTP Handler:
package main
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
)
func TestCreateProduct(t *testing.T) {
// 準備測試資料
payload := Product{Name: "TestItem", Price: 100}
body, _ := json.Marshal(payload)
req := httptest.NewRequest(http.MethodPost, "/products", bytes.NewReader(body))
w := httptest.NewRecorder()
// 直接呼叫 handler(不需要啟動真實伺服器)
createProduct(w, req)
resp := w.Result()
if resp.StatusCode != http.StatusCreated {
t.Fatalf("expected status 201, got %d", resp.StatusCode)
}
var created Product
json.NewDecoder(resp.Body).Decode(&created)
if created.ID == 0 {
t.Fatalf("expected a generated ID")
}
}
httptest.NewRecorder模擬ResponseWriter,讓測試結果可直接檢查。- 測試可在 CI/CD 流程中自動執行,確保 API 行為不會因修改而破壞。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方式 |
|---|---|---|
忘記設定 Content-Type |
客戶端可能無法正確解析 JSON | 在每個回應前 w.Header().Set("Content-Type", "application/json") |
| 直接回傳資料庫錯誤 | 會洩漏內部實作細節,且不符合 REST 錯誤格式 | 使用統一的 APIError 結構,並自行轉換錯誤訊息 |
未處理 Context 取消 |
長時間的 DB 查詢或外部呼叫在客戶端斷線時仍持續執行,浪費資源 | 在 DB、HTTP client 呼叫時傳入 r.Context(),並在查詢前檢查 ctx.Err() |
| 路由衝突或過度耦合 | 多個 handler 共享相同路徑,維護困難 | 使用 gorilla/mux 的子路由(r.PathPrefix("/users").Subrouter())分層管理 |
| 未實作 Pagination | 大量資料一次回傳會導致效能與記憶體問題 | 在列表 API 加入 limit、offset 或 cursor 參數,並在 DB 查詢時使用 |
| CORS 設定過寬 | * 允許所有來源,可能被濫用 |
只允許可信任的前端域名,或使用環境變數動態設定 |
最佳實踐:
- 使用介面抽象:將資料存取層抽成介面 (
Repository),方便單元測試與未來換 DB。 - 統一回應結構:成功回應與錯誤回應皆使用固定 JSON schema,前端只需一套解析程式。
- 加入日誌與追蹤:使用
logrus、zap或 OpenTelemetry,記錄請求 ID、執行時間與錯誤堆疊。 - 環境變數管理:資料庫連線、Port、CORS 來源等設定皆放在
.env或 K8s ConfigMap 中,程式碼不硬編碼。 - 健康檢查端點:提供
/healthz回傳200 OK,讓容器平台(K8s)能自動判斷服務狀態。
實際應用場景
| 場景 | 需求 | 可能的實作方式 |
|---|---|---|
| 行動 App 後端 | 高併發、JWT 認證、檔案上傳 | 使用 net/http + gorilla/mux,JWT 中介層驗證,multipart/form-data 處理檔案 |
| 微服務間資料同步 | 需要可靠的訊息傳遞與重試機制 | 在 API 中加入 Idempotency-Key,配合 Redis 或資料庫鎖定防止重複寫入 |
| 管理後台 CRUD | 複雜的搜尋、分頁、權限控管 | 使用 GORM 結合 gorm.io/plugin/soft_delete,在路由上加入 RBAC 中介層 |
| 公開 API 平台 | 版本管理、速率限制、API 金鑰 | 以 /v1/…、/v2/… 方式分版本,使用 golang.org/x/time/rate 實作速率限制,中介層檢查 API Key |
| 事件驅動系統 | 接收外部 Webhook,驗證簽名 | 在 handler 中使用 HMAC 驗證 X-Signature 標頭,驗證成功後寫入 Kafka 或 NATS |
總結
本文從 RESTful 基礎概念、Go 標準庫與路由框架、JSON 處理、錯誤統一、Middleware、資料庫整合,一路帶到 測試、部署與常見陷阱,提供了完整且實務導向的開發藍圖。只要遵循以下幾點:
- 遵守 REST 原則,讓 API 自然且易於擴充。
- 使用
gorilla/mux或類似框架,簡化路由與參數解析。 - 統一錯誤與回應格式,提升前端開發效率。
- 加入 Middleware(日誌、驗證、CORS),保持程式碼乾淨且可重用。
- 寫測試、使用 CI/CD,確保每次變更不會破壞既有功能。
就能在 Go 生態系中快速打造 高效、可維護、易測試 的 RESTful API,為各種應用(行動、微服務、公開平台)提供堅實的後端支撐。祝你開發順利,打造出令人驕傲的服務!