为什么选择 Go 构建 Web API?
Go 已成为后端 API 和微服务的主流选择,与 Node.js 和 Python 直接竞争。原因很实际:编译为单个二进制文件、无需调优即可获得出色性能、内置并发支持,以及标准库无需第三方框架即可满足约 80% 的 HTTP 需求。
本指南将从零开始构建一个完整的 REST API,涵盖你在生产环境中会使用的模式。

从 net/http 开始
Go 的标准库 net/http 已可用于生产环境:
package main
import (
"encoding/json"
"log"
"net/http"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("GET /users", listUsers)
mux.HandleFunc("GET /users/{id}", getUser) // Go 1.22+ 路径参数
mux.HandleFunc("POST /users", createUser)
server := &http.Server{
Addr: ":8080",
Handler: mux,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 120 * time.Second,
}
log.Println("Starting server on :8080")
log.Fatal(server.ListenAndServe())
}
func listUsers(w http.ResponseWriter, r *http.Request) {
users := []User{
{ID: 1, Name: "Alice", Email: "alice@example.com"},
{ID: 2, Name: "Bob", Email: "bob@example.com"},
}
writeJSON(w, http.StatusOK, users)
}
func getUser(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id") // Go 1.22+
user, err := findUserByID(id)
if err != nil {
writeError(w, http.StatusNotFound, "User not found")
return
}
writeJSON(w, http.StatusOK, user)
}
// 辅助函数
func writeJSON(w http.ResponseWriter, status int, v any) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
if err := json.NewEncoder(w).Encode(v); err != nil {
log.Printf("Error encoding response: %v", err)
}
}
func writeError(w http.ResponseWriter, status int, message string) {
writeJSON(w, status, map[string]string{"error": message})
}
使用 Chi 处理复杂路由
对于更复杂的 API,chi 提供了简洁的路由,无需重型框架:
// go get github.com/go-chi/chi/v5
import (
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
)
func main() {
r := chi.NewRouter()
// 内置中间件
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
r.Use(middleware.RequestID)
r.Use(middleware.RealIP)
// 自定义中间件
r.Use(corsMiddleware)
r.Use(authMiddleware)
// 路由分组
r.Route("/api/v1", func(r chi.Router) {
r.Route("/users", func(r chi.Router) {
r.Get("/", listUsers)
r.Post("/", createUser)
r.Route("/{userID}", func(r chi.Router) {
r.Use(userCtx) // 将用户加载到上下文中
r.Get("/", getUser)
r.Put("/", updateUser)
r.Delete("/", deleteUser)
r.Get("/posts", getUserPosts)
})
})
r.Route("/posts", func(r chi.Router) {
r.Get("/", listPosts)
r.With(requireAuth).Post("/", createPost)
})
})
http.ListenAndServe(":8080", r)
}
中间件模式
// 中间件是一个包装 http.Handler 的函数
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// 包装 ResponseWriter 以捕获状态码
wrapped := &responseWriter{ResponseWriter: w, status: 200}
next.ServeHTTP(wrapped, r)
log.Printf(
"%s %s %d %v %s",
r.Method, r.URL.Path,
wrapped.status,
time.Since(start),
r.RemoteAddr,
)
})
}
type responseWriter struct {
http.ResponseWriter
status int
}
func (rw *responseWriter) WriteHeader(status int) {
rw.status = status
rw.ResponseWriter.WriteHeader(status)
}
// 认证中间件
func authMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
if !strings.HasPrefix(token, "Bearer ") {
writeError(w, http.StatusUnauthorized, "Missing token")
return
}
claims, err := validateJWT(strings.TrimPrefix(token, "Bearer "))
if err != nil {
writeError(w, http.StatusUnauthorized, "Invalid token")
return
}
// 将用户存储到请求上下文中
ctx := context.WithValue(r.Context(), userKey, claims.UserID)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// 从上下文中获取当前用户
func currentUser(r *http.Request) int {
return r.Context().Value(userKey).(int)
}
JSON 请求处理
type CreateUserRequest struct {
Name string `json:"name" validate:"required,min=1,max=100"`
Email string `json:"email" validate:"required,email"`
Age int `json:"age" validate:"gte=0,lte=150"`
}
func createUser(w http.ResponseWriter, r *http.Request) {
var req CreateUserRequest
// 解码 JSON 请求体
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, "Invalid JSON: "+err.Error())
return
}
// 验证(使用 github.com/go-playground/validator)
if err := validate.Struct(req); err != nil {
errors := formatValidationErrors(err)
writeJSON(w, http.StatusUnprocessableEntity, map[string]any{
"error": "Validation failed",
"fields": errors,
})
return
}
user, err := userService.Create(r.Context(), req)
if err != nil {
switch {
case errors.Is(err, ErrEmailTaken):
writeError(w, http.StatusConflict, "Email already registered")
default:
log.Printf("Error creating user: %v", err)
writeError(w, http.StatusInternalServerError, "Internal server error")
}
return
}
writeJSON(w, http.StatusCreated, user)
}
使用 pgx 集成数据库
// go get github.com/jackc/pgx/v5
import "github.com/jackc/pgx/v5/pgxpool"
// 连接池
func newDB(ctx context.Context, connStr string) (*pgxpool.Pool, error) {
config, err := pgxpool.ParseConfig(connStr)
if err != nil {
return nil, err
}
config.MaxConns = 25
config.MinConns = 5
config.MaxConnLifetime = 5 * time.Minute
config.HealthCheckPeriod = 30 * time.Second
return pgxpool.NewWithConfig(ctx, config)
}
// 仓库模式
type UserRepository struct {
db *pgxpool.Pool
}
func (r *UserRepository) FindByID(ctx context.Context, id int) (*User, error) {
var user User
err := r.db.QueryRow(ctx,
"SELECT id, name, email, created_at FROM users WHERE id = $1",
id,
).Scan(&user.ID, &user.Name, &user.Email, &user.CreatedAt)
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrNotFound
}
return nil, fmt.Errorf("FindByID: %w", err)
}
return &user, nil
}
func (r *UserRepository) List(ctx context.Context, limit, offset int) ([]User, error) {
rows, err := r.db.Query(ctx,
"SELECT id, name, email FROM users ORDER BY created_at DESC LIMIT $1 OFFSET $2",
limit, offset,
)
if err != nil {
return nil, err
}
defer rows.Close()
users := make([]User, 0)
for rows.Next() {
var u User
if err := rows.Scan(&u.ID, &u.Name, &u.Email); err != nil {
return nil, err
}
users = append(users, u)
}
return users, rows.Err()
}
// 事务
func (r *UserRepository) Transfer(ctx context.Context, fromID, toID int, amount float64) error {
tx, err := r.db.Begin(ctx)
if err != nil {
return err
}
defer tx.Rollback(ctx) // 如果已提交则无操作
_, err = tx.Exec(ctx,
"UPDATE accounts SET balance = balance - $1 WHERE user_id = $2",
amount, fromID,
)
if err != nil {
return fmt.Errorf("debit: %w", err)
}
_, err = tx.Exec(ctx,
"UPDATE accounts SET balance = balance + $1 WHERE user_id = $2",
amount, toID,
)
if err != nil {
return fmt.Errorf("credit: %w", err)
}
return tx.Commit(ctx)
}
错误处理模式
// 哨兵错误——用于比较的特定错误值
var (
ErrNotFound = errors.New("not found")
ErrEmailTaken = errors.New("email already taken")
ErrUnauthorized = errors.New("unauthorized")
)
// 包装错误——在保留原始错误的同时添加上下文
func getUser(id int) (*User, error) {
user, err := db.QueryUser(id)
if err != nil {
return nil, fmt.Errorf("getUser(%d): %w", id, err) // %w 包装错误
}
return user, nil
}
// 使用 errors.Is 检查错误类型(可穿透包装链)
err := getUser(123)
if errors.Is(err, sql.ErrNoRows) {
// 处理未找到
}
// 自定义错误类型以提供丰富的错误信息
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("validation error: %s - %s", e.Field, e.Message)
}
// 使用 errors.As 检查
var valErr *ValidationError
if errors.As(err, &valErr) {
fmt.Printf("Field %s: %s", valErr.Field, valErr.Message)
}
项目结构
myapi/
├── cmd/
│ └── api/
│ └── main.go # 入口点
├── internal/
│ ├── handler/ # HTTP 处理器
│ │ ├── users.go
│ │ └── posts.go
│ ├── service/ # 业务逻辑
│ │ ├── user_service.go
│ │ └── post_service.go
│ ├── repository/ # 数据访问
│ │ ├── user_repo.go
│ │ └── post_repo.go
│ ├── middleware/ # HTTP 中间件
│ │ ├── auth.go
│ │ └── logging.go
│ └── model/ # 领域类型
│ ├── user.go
│ └── post.go
├── config/
│ └── config.go # 配置加载
├── go.mod
└── go.sum
Go 的 Web 开发方法令人耳目一新地务实——极简的标准库、显式的错误处理和直接的并发支持。缺乏“魔法”(默认没有 ORM、没有 DI 容器)使得 Go 代码易于阅读和调试,即使在大型代码库中也是如此。
→ 使用 URL 编码/解码工具 对 URL 参数进行编码和解码。