chi

Deep Knowledge: Use mcp__documentation__fetch_docs with technology: chi for comprehensive documentation.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "chi" with this command: npx skills add claude-dev-suite/claude-dev-suite/claude-dev-suite-claude-dev-suite-chi

Chi Core Knowledge

Deep Knowledge: Use mcp__documentation__fetch_docs with technology: chi for comprehensive documentation.

Basic Setup

package main

import ( "net/http" "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.Get("/", func(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("Hello, World!"))
})

http.ListenAndServe(":8080", r)

}

Routing

Basic Routes

r := chi.NewRouter()

r.Get("/users", listUsers) r.Get("/users/{id}", getUser) r.Post("/users", createUser) r.Put("/users/{id}", updateUser) r.Delete("/users/{id}", deleteUser)

// Method not allowed handler r.MethodNotAllowed(methodNotAllowedHandler)

// Not found handler r.NotFound(notFoundHandler)

Path Parameters

r.Get("/users/{userID}", func(w http.ResponseWriter, r *http.Request) { userID := chi.URLParam(r, "userID") json.NewEncoder(w).Encode(map[string]string{"id": userID}) })

// Regex constraints r.Get("/articles/{date:\d{4}-\d{2}-\d{2}}", getArticleByDate)

// Catch-all r.Get("/files/*", serveFiles)

Route Groups

r := chi.NewRouter()

r.Route("/api", func(r chi.Router) { r.Route("/v1", func(r chi.Router) { r.Get("/users", listUsersV1) r.Post("/users", createUserV1) })

r.Route("/v2", func(r chi.Router) {
    r.Get("/users", listUsersV2)
    r.Post("/users", createUserV2)
})

})

Subrouters

r := chi.NewRouter()

// Mount subrouter apiRouter := chi.NewRouter() apiRouter.Get("/users", listUsers) apiRouter.Post("/users", createUser)

r.Mount("/api/v1", apiRouter)

Context

Request Context

func getUser(w http.ResponseWriter, r *http.Request) { // Path params userID := chi.URLParam(r, "userID")

// Query params
page := r.URL.Query().Get("page")

// Headers
auth := r.Header.Get("Authorization")

// Context values
user := r.Context().Value("user").(*User)

json.NewEncoder(w).Encode(user)

}

Context with Values

func UserContext(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { userID := chi.URLParam(r, "userID") user, err := findUser(userID) if err != nil { http.Error(w, "User not found", http.StatusNotFound) return }

    ctx := context.WithValue(r.Context(), "user", user)
    next.ServeHTTP(w, r.WithContext(ctx))
})

}

r.Route("/users/{userID}", func(r chi.Router) { r.Use(UserContext) r.Get("/", getUser) r.Put("/", updateUser) })

Middleware

Built-in Middleware

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

r := chi.NewRouter()

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

Custom Middleware

func TimingMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { start := time.Now() next.ServeHTTP(w, r) duration := time.Since(start) log.Printf("%s %s %v", r.Method, r.URL.Path, duration) }) }

r.Use(TimingMiddleware)

Authentication Middleware

func AuthMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { auth := r.Header.Get("Authorization")

    if auth == "" {
        http.Error(w, "Unauthorized", http.StatusUnauthorized)
        return
    }

    if !strings.HasPrefix(auth, "Bearer ") {
        http.Error(w, "Invalid token format", http.StatusUnauthorized)
        return
    }

    token := auth[7:]
    user, err := validateToken(token)
    if err != nil {
        http.Error(w, "Invalid token", http.StatusUnauthorized)
        return
    }

    ctx := context.WithValue(r.Context(), "user", user)
    next.ServeHTTP(w, r.WithContext(ctx))
})

}

r.Route("/api", func(r chi.Router) { r.Use(AuthMiddleware) r.Get("/me", getMe) })

func getMe(w http.ResponseWriter, r *http.Request) { user := r.Context().Value("user").(*User) json.NewEncoder(w).Encode(user) }

CORS Middleware

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

r.Use(cors.Handler(cors.Options{ AllowedOrigins: []string{"https://example.com"}, AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, AllowedHeaders: []string{"Accept", "Authorization", "Content-Type"}, AllowCredentials: true, MaxAge: 300, }))

Response Helpers

render Package

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

type UserResponse struct { ID string json:"id" Name string json:"name" Email string json:"email" }

func (u *UserResponse) Render(w http.ResponseWriter, r *http.Request) error { return nil }

func getUser(w http.ResponseWriter, r *http.Request) { user := &UserResponse{ ID: "1", Name: "Alice", Email: "alice@example.com", } render.JSON(w, r, user) }

// Error response type ErrResponse struct { Err error json:"-" HTTPStatusCode int json:"-" StatusText string json:"status" ErrorText string json:"error,omitempty" }

func (e *ErrResponse) Render(w http.ResponseWriter, r *http.Request) error { render.Status(r, e.HTTPStatusCode) return nil }

func ErrNotFound(err error) render.Renderer { return &ErrResponse{ Err: err, HTTPStatusCode: http.StatusNotFound, StatusText: "Resource not found", ErrorText: err.Error(), } }

Manual JSON Response

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

func getUser(w http.ResponseWriter, r *http.Request) { user := User{ID: "1", Name: "Alice"} respond(w, http.StatusOK, user) }

Error Handling

type APIError struct { StatusCode int json:"-" Message string json:"message" Details string json:"details,omitempty" }

func (e *APIError) Error() string { return e.Message }

func errorHandler(w http.ResponseWriter, r *http.Request, err error) { var apiErr *APIError if errors.As(err, &apiErr) { respond(w, apiErr.StatusCode, apiErr) return }

respond(w, http.StatusInternalServerError, map[string]string{
    "message": "Internal server error",
})

}

func getUser(w http.ResponseWriter, r *http.Request) { userID := chi.URLParam(r, "userID") user, err := findUser(userID) if err != nil { errorHandler(w, r, &APIError{ StatusCode: http.StatusNotFound, Message: "User not found", }) return } respond(w, http.StatusOK, user) }

Patterns

Resource Pattern

type UsersResource struct { db *sql.DB }

func (rs UsersResource) Routes() chi.Router { r := chi.NewRouter()

r.Get("/", rs.List)
r.Post("/", rs.Create)

r.Route("/{id}", func(r chi.Router) {
    r.Use(rs.UserCtx)
    r.Get("/", rs.Get)
    r.Put("/", rs.Update)
    r.Delete("/", rs.Delete)
})

return r

}

func (rs UsersResource) UserCtx(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { id := chi.URLParam(r, "id") user, err := rs.db.FindUser(id) if err != nil { http.Error(w, "User not found", http.StatusNotFound) return } ctx := context.WithValue(r.Context(), "user", user) next.ServeHTTP(w, r.WithContext(ctx)) }) }

// Mount resource r.Mount("/users", UsersResource{db: db}.Routes())

Production Readiness

Health Checks

r.Get("/health", func(w http.ResponseWriter, r *http.Request) { respond(w, http.StatusOK, map[string]string{"status": "healthy"}) })

r.Get("/ready", func(w http.ResponseWriter, r http.Request) { ctx, cancel := context.WithTimeout(r.Context(), 2time.Second) defer cancel()

if err := db.PingContext(ctx); err != nil {
    respond(w, http.StatusServiceUnavailable, map[string]string{
        "status":   "not ready",
        "database": "disconnected",
    })
    return
}

respond(w, http.StatusOK, map[string]string{
    "status":   "ready",
    "database": "connected",
})

})

Graceful Shutdown

func main() { r := chi.NewRouter() r.Get("/", handler)

srv := &http.Server{
    Addr:    ":8080",
    Handler: r,
}

go func() {
    if err := srv.ListenAndServe(); err != http.ErrServerClosed {
        log.Fatal(err)
    }
}()

quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

if err := srv.Shutdown(ctx); err != nil {
    log.Fatal(err)
}

}

Checklist

  • Recoverer middleware enabled

  • CORS configured

  • Request timeout set

  • Throttling enabled

  • Health/readiness endpoints

  • Graceful shutdown

  • Structured logging

  • Context-based values

When NOT to Use This Skill

  • Gin projects - Gin has more built-in features

  • Echo projects - Echo has different context patterns

  • Fiber projects - Fiber is Express-like and faster

  • Need built-in validation - Chi is minimal, use Gin or Echo

  • WebSocket-heavy apps - Other frameworks have better support

Anti-Patterns

Anti-Pattern Why It's Bad Solution

Not using chi.URLParam()

Manual param extraction Use chi.URLParam(r, "id")

Missing middleware order Wrong execution sequence Order middleware carefully

Not using context for values Global state issues Use context.WithValue()

Forgetting to call next.ServeHTTP()

Handler chain breaks Always call next.ServeHTTP(w, r)

No structured error handling Inconsistent responses Create helper functions

Using subrouters incorrectly Routes don't match Use .Route() or .Mount() properly

Quick Troubleshooting

Problem Diagnosis Fix

Route not matching Wrong path pattern Check regex constraints and params

Middleware not executing Order issue Place middleware before routes

Context value is nil Not set properly Use context.WithValue() in middleware

CORS errors Not configured Use cors.Handler() from go-chi/cors

Panic on missing param No default Check chi.URLParam() return value

405 Method Not Allowed Wrong HTTP method Verify route method matches request

Reference Documentation

  • Routing

  • Middleware

  • Patterns

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

cron-scheduling

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

token-optimization

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

react-19

No summary provided by upstream source.

Repository SourceNeeds Review