| name | Chi Router Development |
| description | This skill should be used when the user asks to "setup Chi router", "create Go API", "Chi middleware", "REST API handlers", "Go HTTP routing", or works with Chi v5 web framework for x-project-v2 backend. |
| version | 0.1.0 |
Chi Router Development for x-project-v2
Chi is a lightweight, idiomatic and composable router for building Go HTTP services.
Architecture
cmd/server/main.go # Entry point, router setup
internal/
โโโ handler/ # HTTP handlers
โ โโโ handler.go # Base handler with utilities
โ โโโ lists.go # List handlers
โ โโโ tasks.go # Task handlers
โโโ middleware/ # Custom middleware
โ โโโ logger.go # Request logging
โโโ routes/ # Route registry
โโโ routes.go # Route registration pattern
Router Setup
package main
import (
"github.com/go-chi/chi/v5"
chimiddleware "github.com/go-chi/chi/v5/middleware"
"github.com/go-chi/cors"
)
func main() {
r := chi.NewRouter()
r.Use(chimiddleware.RequestID)
r.Use(chimiddleware.RealIP)
r.Use(middleware.SanitizedLogger)
r.Use(chimiddleware.Recoverer)
r.Use(cors.Handler(cors.Options{
AllowedOrigins: []string{"http://localhost:3000"},
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
}))
r.Get("/health", h.Health)
r.Route("/api", func(r chi.Router) {
r.Get("/lists", h.ListLists)
r.Post("/lists", h.CreateList)
r.Get("/lists/{listId}", h.GetList)
})
}
Handler Pattern
package handler
import (
"encoding/json"
"net/http"
"github.com/go-chi/chi/v5"
)
type Handler struct {
listService *service.ListService
taskService *service.TaskService
}
func New(s *store.Store, cfg *config.Config, ls *service.ListService, ts *service.TaskService) *Handler {
return &Handler{listService: ls, taskService: ts}
}
func (h *Handler) JSON(w http.ResponseWriter, status int, data interface{}) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
json.NewEncoder(w).Encode(data)
}
func (h *Handler) Error(w http.ResponseWriter, status int, message string) {
h.JSON(w, status, map[string]string{"error": message})
}
func (h *Handler) DecodeJSON(r *http.Request, v interface{}) error {
return json.NewDecoder(r.Body).Decode(v)
}
Handler Implementation
func (h *Handler) ListLists(w http.ResponseWriter, r *http.Request) {
lists, err := h.listService.ListLists(r.Context())
if err != nil {
h.Error(w, http.StatusInternalServerError, "Failed to list lists")
return
}
h.JSON(w, http.StatusOK, map[string]interface{}{"lists": lists})
}
func (h *Handler) GetList(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "listId")
list, err := h.listService.GetList(r.Context(), id)
if err != nil {
if err == store.ErrNotFound {
h.Error(w, http.StatusNotFound, "List not found")
return
}
h.Error(w, http.StatusInternalServerError, "Failed to get list")
return
}
h.JSON(w, http.StatusOK, list)
}
func (h *Handler) CreateList(w http.ResponseWriter, r *http.Request) {
var req struct {
Name string `json:"name"`
}
if err := h.DecodeJSON(r, &req); err != nil {
h.Error(w, http.StatusBadRequest, "Invalid request body")
return
}
list, err := h.listService.CreateList(r.Context(), req.Name)
if err != nil {
h.Error(w, http.StatusBadRequest, err.Error())
return
}
h.JSON(w, http.StatusCreated, list)
}
func (h *Handler) UpdateList(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "listId")
var req struct {
Name string `json:"name"`
}
if err := h.DecodeJSON(r, &req); err != nil {
h.Error(w, http.StatusBadRequest, "Invalid request body")
return
}
list, err := h.listService.UpdateList(r.Context(), id, req.Name)
if err != nil {
if err == store.ErrNotFound {
h.Error(w, http.StatusNotFound, "List not found")
return
}
h.Error(w, http.StatusBadRequest, err.Error())
return
}
h.JSON(w, http.StatusOK, list)
}
func (h *Handler) DeleteList(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "listId")
if err := h.listService.DeleteList(r.Context(), id); err != nil {
if err == store.ErrNotFound {
h.Error(w, http.StatusNotFound, "List not found")
return
}
h.Error(w, http.StatusBadRequest, err.Error())
return
}
h.JSON(w, http.StatusOK, map[string]bool{"success": true})
}
Middleware
package middleware
import (
"fmt"
"net/http"
"time"
"github.com/go-chi/chi/v5/middleware"
)
func SanitizedLogger(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
ww := middleware.NewWrapResponseWriter(w, r.ProtoMajor)
next.ServeHTTP(ww, r)
fmt.Printf("%s %s %s %d %s\n",
r.Method,
r.URL.Path,
r.RemoteAddr,
ww.Status(),
time.Since(start),
)
})
}
Route Registry Pattern
package routes
import "github.com/go-chi/chi/v5"
type Route struct {
Method string
Pattern string
Handler http.HandlerFunc
Meta Meta
}
type Meta struct {
Group string
Description string
}
type Registry struct {
routes []Route
prefix string
}
func GetRegistry() *Registry {
return &Registry{routes: make([]Route, 0)}
}
func (r *Registry) WithPrefix(prefix string) *Registry {
return &Registry{routes: r.routes, prefix: prefix}
}
func (r *Registry) Register(router chi.Router, route Route) {
route.Pattern = r.prefix + route.Pattern
}
HTTP Status Codes
| Code | Usage |
|---|
| 200 OK | GET, PUT success |
| 201 Created | POST success (new resource) |
| 400 Bad Request | Validation error |
| 404 Not Found | Resource not found |
| 500 Internal Server Error | Server error |
Error Handling
if err != nil {
if err == store.ErrNotFound {
h.Error(w, http.StatusNotFound, "List not found")
return
}
h.Error(w, http.StatusInternalServerError, "Database error")
return
}
list, err := h.listService.CreateList(r.Context(), req.Name)
if err != nil {
h.Error(w, http.StatusBadRequest, err.Error())
return
}