Chi

Setup & Router

import "github.com/go-chi/chi/v5"

func main() {
    r := chi.NewRouter()
    r.Use(middleware.Logger)
    r.Use(middleware.Recoverer)
    http.ListenAndServe(":3000", r)
}

Routes

r.Get("/", func(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("hello"))
})
r.Post("/users", createUser)
r.Put("/users/{id}", updateUser)
r.Delete("/users/{id}", deleteUser)
r.Patch("/users/{id}", patchUser)

r.MethodFunc("GET", "/health", func(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("ok"))
})

Route Groups

r.Route("/api", func(r chi.Router) {
    r.Get("/status", getStatus)
    r.Route("/users", func(r chi.Router) {
        r.Get("/", listUsers)
        r.Post("/", createUser)
        r.Route("/{id}", func(r chi.Router) {
            r.Get("/", getUser)
            r.Delete("/", deleteUser)
        })
    })
})

r.Mount("/admin", adminRouter())

Middleware

r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
r.Use(middleware.RequestID)
r.Use(middleware.RealIP)
r.Use(middleware.Timeout(60 * time.Second))
r.Use(middleware.Throttle(100))
r.Use(middleware.Compress(5))
r.Use(middleware.Heartbeat("/ping"))

URL Parameters

r.Get("/users/{id}", func(w http.ResponseWriter, r *http.Request) {
    id := chi.URLParam(r, "id")
    fmt.Fprintf(w, "user: %s", id)
})

r.Get("/articles/{slug}", func(w http.ResponseWriter, r *http.Request) {
    slug := chi.URLParam(r, "slug")
    fmt.Fprintf(w, "article: %s", slug)
})

Request Binding

type CreateUserRequest struct {
    Name  string `json:"name"`
    Email string `json:"email"`
}

r.Post("/users", func(w http.ResponseWriter, r *http.Request) {
    var req CreateUserRequest
    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    defer r.Body.Close()
    json.NewEncoder(w).Encode(req)
})

JSON Response

func renderJSON(w http.ResponseWriter, v interface{}, status int) {
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(status)
    json.NewEncoder(w).Encode(v)
}

r.Get("/users", func(w http.ResponseWriter, r *http.Request) {
    users := []User{{ID: 1, Name: "Alice"}, {ID: 2, Name: "Bob"}}
    renderJSON(w, users, http.StatusOK)
})

r.Get("/error", func(w http.ResponseWriter, r *http.Request) {
    renderJSON(w, map[string]string{"error": "not found"}, http.StatusNotFound)
})

File Server

r.Get("/static/*", func(w http.ResponseWriter, r *http.Request) {
    fs := http.StripPrefix("/static", http.FileServer(http.Dir("./static")))
    fs.ServeHTTP(w, r)
})

workDir, _ := os.Getwd()
filesDir := http.Dir(filepath.Join(workDir, "files"))
r.Get("/files/*", http.FileServer(filesDir).ServeHTTP)

Context Values

r.Use(func(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        ctx := context.WithValue(r.Context(), "userID", 42)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
})

r.Get("/me", func(w http.ResponseWriter, r *http.Request) {
    userID := r.Context().Value("userID").(int)
    fmt.Fprintf(w, "user: %d", userID)
})

Custom Middleware

func AuthMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        token := r.Header.Get("Authorization")
        if token == "" {
            http.Error(w, "unauthorized", http.StatusUnauthorized)
            return
        }
        ctx := context.WithValue(r.Context(), "token", token)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

r.With(AuthMiddleware).Get("/protected", func(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("secret data"))
})

r.Group(func(r chi.Router) {
    r.Use(AuthMiddleware)
    r.Get("/dashboard", getDashboard)
    r.Get("/settings", getSettings)
})

Migrating from stdlib

r := chi.NewRouter()
r.Use(middleware.Logger)

r.HandleFunc("/legacy", legacyHandler)
r.Get("/new", newHandler)

http.ListenAndServe(":3000", r)

r.NotFound(func(w http.ResponseWriter, r *http.Request) {
    http.Error(w, "custom 404", http.StatusNotFound)
})

r.MethodNotAllowed(func(w http.ResponseWriter, r *http.Request) {
    http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
})

初始化与路由

import "github.com/go-chi/chi/v5"

func main() {
    r := chi.NewRouter()
    r.Use(middleware.Logger)
    r.Use(middleware.Recoverer)
    http.ListenAndServe(":3000", r)
}

路由定义

r.Get("/", func(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("hello"))
})
r.Post("/users", createUser)
r.Put("/users/{id}", updateUser)
r.Delete("/users/{id}", deleteUser)
r.Patch("/users/{id}", patchUser)

r.MethodFunc("GET", "/health", func(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("ok"))
})

路由分组

r.Route("/api", func(r chi.Router) {
    r.Get("/status", getStatus)
    r.Route("/users", func(r chi.Router) {
        r.Get("/", listUsers)
        r.Post("/", createUser)
        r.Route("/{id}", func(r chi.Router) {
            r.Get("/", getUser)
            r.Delete("/", deleteUser)
        })
    })
})

r.Mount("/admin", adminRouter())  // 挂载子路由器

中间件

r.Use(middleware.Logger)          // 请求日志
r.Use(middleware.Recoverer)       // panic 恢复
r.Use(middleware.RequestID)       // 请求 ID
r.Use(middleware.RealIP)          // 真实 IP
r.Use(middleware.Timeout(60 * time.Second))  // 超时控制
r.Use(middleware.Throttle(100))   // 限流
r.Use(middleware.Compress(5))     // gzip 压缩
r.Use(middleware.Heartbeat("/ping"))  // 健康检查

URL 参数

r.Get("/users/{id}", func(w http.ResponseWriter, r *http.Request) {
    id := chi.URLParam(r, "id")
    fmt.Fprintf(w, "user: %s", id)
})

r.Get("/articles/{slug}", func(w http.ResponseWriter, r *http.Request) {
    slug := chi.URLParam(r, "slug")
    fmt.Fprintf(w, "article: %s", slug)
})

请求绑定

type CreateUserRequest struct {
    Name  string `json:"name"`
    Email string `json:"email"`
}

r.Post("/users", func(w http.ResponseWriter, r *http.Request) {
    var req CreateUserRequest
    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    defer r.Body.Close()
    json.NewEncoder(w).Encode(req)
})

JSON 响应

func renderJSON(w http.ResponseWriter, v interface{}, status int) {
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(status)
    json.NewEncoder(w).Encode(v)
}

r.Get("/users", func(w http.ResponseWriter, r *http.Request) {
    users := []User{{ID: 1, Name: "Alice"}, {ID: 2, Name: "Bob"}}
    renderJSON(w, users, http.StatusOK)
})

r.Get("/error", func(w http.ResponseWriter, r *http.Request) {
    renderJSON(w, map[string]string{"error": "not found"}, http.StatusNotFound)
})

静态文件服务

r.Get("/static/*", func(w http.ResponseWriter, r *http.Request) {
    fs := http.StripPrefix("/static", http.FileServer(http.Dir("./static")))
    fs.ServeHTTP(w, r)
})

workDir, _ := os.Getwd()
filesDir := http.Dir(filepath.Join(workDir, "files"))
r.Get("/files/*", http.FileServer(filesDir).ServeHTTP)

Context 传值

r.Use(func(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        ctx := context.WithValue(r.Context(), "userID", 42)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
})

r.Get("/me", func(w http.ResponseWriter, r *http.Request) {
    userID := r.Context().Value("userID").(int)
    fmt.Fprintf(w, "user: %d", userID)
})

自定义中间件

func AuthMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        token := r.Header.Get("Authorization")
        if token == "" {
            http.Error(w, "unauthorized", http.StatusUnauthorized)
            return
        }
        ctx := context.WithValue(r.Context(), "token", token)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

r.With(AuthMiddleware).Get("/protected", func(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("secret data"))
})

r.Group(func(r chi.Router) {
    r.Use(AuthMiddleware)
    r.Get("/dashboard", getDashboard)
    r.Get("/settings", getSettings)
})

从标准库迁移

r := chi.NewRouter()
r.Use(middleware.Logger)

r.HandleFunc("/legacy", legacyHandler)  // 兼容 http.HandleFunc
r.Get("/new", newHandler)

http.ListenAndServe(":3000", r)

r.NotFound(func(w http.ResponseWriter, r *http.Request) {
    http.Error(w, "custom 404", http.StatusNotFound)
})

r.MethodNotAllowed(func(w http.ResponseWriter, r *http.Request) {
    http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
})