Skip to main content

Command Palette

Search for a command to run...

Golang - Chi - Cheatsheet

Updated
3 min read
Golang - Chi - Cheatsheet
T

Just a guy who loves to write code and watch anime.

Installation

go get github.com/go-chi/chi/v5

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("/", homeHandler)
    http.ListenAndServe(":8080", r)
}

func homeHandler(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("Hello World!"))
}

HTTP Methods

r.Get("/users", getUsers)
r.Post("/users", createUser)
r.Put("/users/{id}", updateUser)
r.Delete("/users/{id}", deleteUser)
r.Patch("/users/{id}", patchUser)

URL Parameters

// Route: /users/{id}
func getUser(w http.ResponseWriter, r *http.Request) {
    userID := chi.URLParam(r, "id")
    // userID = "123" for URL: /users/123
}

// Multiple params: /articles/{date}-{slug}
date := chi.URLParam(r, "date")
slug := chi.URLParam(r, "slug")

// Wildcards: /files/*
filepath := chi.URLParam(r, "*")

// Regex: /posts/{id:^[0-9]+$}
r.Get("/posts/{id:^[0-9]+$}", getPost)

Route Groups & Sub-Routers

// Method 1: Route groups
r.Route("/api/v1", func(r chi.Router) {
    r.Get("/users", getUsers)     // GET /api/v1/users
    r.Post("/users", createUser)  // POST /api/v1/users
})

// Method 2: Mounting
apiRouter := chi.NewRouter()
apiRouter.Get("/users", getUsers)
r.Mount("/api/v1", apiRouter)

Middleware

// Global middleware
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
r.Use(middleware.Timeout(60 * time.Second))

// Group-specific middleware
r.Group(func(r chi.Router) {
    r.Use(AuthMiddleware)
    r.Get("/profile", getProfile)
})

// 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", 401)
            return
        }
        next.ServeHTTP(w, r)
    })
}

Common Middleware

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

// CORS
r.Use(cors.Handler(cors.Options{
    AllowedOrigins: []string{"*"},
    AllowedMethods: []string{"GET", "POST", "PUT", "DELETE"},
    AllowedHeaders: []string{"Accept", "Authorization", "Content-Type"},
}))

// Rate limiting
r.Use(httprate.LimitByIP(100, 1*time.Minute))

// Built-in middleware
r.Use(middleware.Compress(5))           // Gzip compression
r.Use(middleware.RealIP)               // Real IP behind proxy
r.Use(middleware.Throttle(50))         // Max 50 concurrent requests
r.Use(middleware.NoCache)              // No cache headers

Context Usage

// Setting context in middleware
func UserMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        user := getUserFromToken(r)
        ctx := context.WithValue(r.Context(), "user", user)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

// Getting from context in handler
func getProfile(w http.ResponseWriter, r *http.Request) {
    user := r.Context().Value("user").(*User)
    json.NewEncoder(w).Encode(user)
}

JSON Handling

func createUser(w http.ResponseWriter, r *http.Request) {
    var user User
    if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
        http.Error(w, "Invalid JSON", 400)
        return
    }

    // Process user...

    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(201)
    json.NewEncoder(w).Encode(user)
}

Error Handling

// Custom 404/405 handlers
r.NotFound(func(w http.ResponseWriter, r *http.Request) {
    w.WriteHeader(404)
    w.Write([]byte("Route not found"))
})

r.MethodNotAllowed(func(w http.ResponseWriter, r *http.Request) {
    w.WriteHeader(405)
    w.Write([]byte("Method not allowed"))
})

Testing

func TestHandler(t *testing.T) {
    r := chi.NewRouter()
    r.Get("/test", testHandler)

    req := httptest.NewRequest("GET", "/test", nil)
    rr := httptest.NewRecorder()

    r.ServeHTTP(rr, req)

    assert.Equal(t, 200, rr.Code)
    assert.Equal(t, "expected", rr.Body.String())
}

Real-World Structure

type Server struct {
    Router *chi.Mux
    DB     *sql.DB
}

func NewServer(db *sql.DB) *Server {
    s := &Server{DB: db}
    s.Router = chi.NewRouter()
    s.setupMiddleware()
    s.setupRoutes()
    return s
}

func (s *Server) setupMiddleware() {
    s.Router.Use(middleware.Logger)
    s.Router.Use(middleware.Recoverer)
    s.Router.Use(cors.Handler(corsOptions))
}

func (s *Server) setupRoutes() {
    // Public routes
    s.Router.Group(func(r chi.Router) {
        r.Get("/", s.home)
        r.Post("/login", s.login)
    })

    // Protected routes
    s.Router.Group(func(r chi.Router) {
        r.Use(s.authMiddleware)
        r.Route("/api", func(r chi.Router) {
            r.Get("/users", s.getUsers)
            r.Post("/users", s.createUser)
        })
    })
}

Common Patterns

// Health check
r.Get("/health", func(w http.ResponseWriter, r *http.Request) {
    w.WriteHeader(200)
    w.Write([]byte("OK"))
})

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

func FileServer(r chi.Router, path string, root http.FileSystem) {
    fs := http.StripPrefix(path, http.FileServer(root))
    r.Get(path+"/*", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        fs.ServeHTTP(w, r)
    }))
}

// Graceful shutdown
server := &http.Server{Addr: ":8080", Handler: r}
go server.ListenAndServe()

c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
<-c

ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
server.Shutdown(ctx)