本文 AI 產出,尚未審核

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 同時完成序列化與寫入,避免手動 MarshalWrite 的繁瑣。

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 加入 limitoffsetcursor 參數,並在 DB 查詢時使用
CORS 設定過寬 * 允許所有來源,可能被濫用 只允許可信任的前端域名,或使用環境變數動態設定

最佳實踐

  1. 使用介面抽象:將資料存取層抽成介面 (Repository),方便單元測試與未來換 DB。
  2. 統一回應結構:成功回應與錯誤回應皆使用固定 JSON schema,前端只需一套解析程式。
  3. 加入日誌與追蹤:使用 logruszap 或 OpenTelemetry,記錄請求 ID、執行時間與錯誤堆疊。
  4. 環境變數管理:資料庫連線、Port、CORS 來源等設定皆放在 .env 或 K8s ConfigMap 中,程式碼不硬編碼。
  5. 健康檢查端點:提供 /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、資料庫整合,一路帶到 測試、部署與常見陷阱,提供了完整且實務導向的開發藍圖。只要遵循以下幾點:

  1. 遵守 REST 原則,讓 API 自然且易於擴充。
  2. 使用 gorilla/mux 或類似框架,簡化路由與參數解析。
  3. 統一錯誤與回應格式,提升前端開發效率。
  4. 加入 Middleware(日誌、驗證、CORS),保持程式碼乾淨且可重用。
  5. 寫測試、使用 CI/CD,確保每次變更不會破壞既有功能。

就能在 Go 生態系中快速打造 高效、可維護、易測試 的 RESTful API,為各種應用(行動、微服務、公開平台)提供堅實的後端支撐。祝你開發順利,打造出令人驕傲的服務!