| 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
}