Skip to main content
skill CX Codex

golang-http-frameworks — Claude Code skill: setup and usage

Provides architecture patterns and routing guidance for Go REST APIs using net/http, Chi, Gin, Echo, or Fiber. Useful when designing endpoints, middleware, and httptest suites.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add bobmatnyc/claude-mpm-skills --skill golang-http-frameworks -a claude-code
Open on GitHub

What golang-http-frameworks does

This skill guides REST API development across the Go ecosystem. It covers routing with the standard library net/http package alongside third-party options such as Chi, Gin, Echo, and Fiber. The guidance outlines composable middleware chains, request validation using struct tags, and integration testing with httptest.Server.

Standard library routing in Go 1.22+ handles path parameters directly in ServeMux. You do not need external routing dependencies for straightforward services. When an API demands automatic request binding or nested route groups, the skill details clean migration paths to Chi or Gin.

What it helps with

  • choose an HTTP router or framework matching project needs
  • configure path parameter routing in Go 1.22+ ServeMux
  • build middleware chains for request logging, panic recovery, and authentication
  • verify HTTP handlers using httptest.Server integration tests
  • apply struct tags for incoming JSON payload validation

Example prompts for the agent

  • Set up a user management REST API using Chi with custom context middleware.
  • Refactor these Gin handlers to standard net/http with Go 1.22 routing patterns.
  • Write an integration test for our authentication endpoint using httptest.

How to install golang-http-frameworks

1Skills CLI (Vercel)

npx skills add bobmatnyc/claude-mpm-skills --skill golang-http-frameworks -a claude-code

2Manually: put the files in place

~/.claude/skills/golang-http-frameworks/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/golang-http-frameworks/

Step by step, with commands for each platform: How to install skills in Claude Code: five ways

Questions about golang-http-frameworks

How do I install golang-http-frameworks in Claude Code?

Run npx skills add bobmatnyc/claude-mpm-skills --skill golang-http-frameworks -a claude-code in your terminal.

Which Go frameworks does this skill cover?

It covers the standard library net/http package with Go 1.22+ routing, as well as Chi, Gin, Echo, and Fiber.

When should I prefer net/http over external routers?

Standard library net/http avoids third-party dependencies and supports parameterized routes via ServeMux starting in Go 1.22. It works well when minimal external code and strict stability are priorities.

Source text

SKILL.md · MIT
---
name: golang-http-frameworks
description: "Go HTTP API development with net/http, Chi, Gin, Echo, and Fiber frameworks"
user-invocable: false
disable-model-invocation: true
version: 1.1.0
updated: "2026-06-15"
category: toolchain
author: Claude MPM Team
license: MIT
progressive_disclosure:
  entry_point:
    summary: "Master Go HTTP frameworks from stdlib net/http to Chi, Gin, Echo, and Fiber for building production-ready REST APIs with middleware, validation, and client patterns"
    when_to_use: "Building RESTful APIs, choosing HTTP framework, implementing authentication middleware, designing REST endpoints, testing HTTP handlers, optimizing API performance"
    quick_start: "1. Choose framework (stdlib, Chi, Gin, Echo, Fiber) 2. Structure routes and middleware 3. Implement request validation 4. Design error handling 5. Test with httptest"
  references:
    - go-idioms-quality.md
  token_estimate:
    entry: 160
    full: 5000
context_limit: 700
tags:
  - http
  - golang
  - chi
  - gin
  - echo
  - fiber
  - rest-api
  - middleware
requires_tools: []
---

# Go HTTP Frameworks & REST APIs

## Overview

Go provides exceptional HTTP capabilities starting with the standard library's `net/http` package. Go 1.22+ introduced enhanced pattern routing in `ServeMux`, making stdlib viable for many applications. For more complex needs, frameworks like Chi, Gin, Echo, and Fiber offer additional features while maintaining Go's simplicity and performance.

**Key Features:**
- 🌐 **net/http**: Production-ready standard library with Go 1.22+ routing
- 🎯 **Chi**: Lightweight, stdlib-compatible router with middleware chains
- ⚡ **Gin**: High-performance framework with binding and validation
- 🛡️ **Echo**: Type-safe, enterprise framework with OpenAPI support
- 🚀 **Fiber**: Express.js-inspired framework with WebSocket support
- 🔧 **Middleware**: Composable request/response processing
- ✅ **Validation**: Struct tag-based request validation
- 🧪 **Testing**: httptest.Server for comprehensive integration tests

## When to Use This Skill

Activate this skill when:
- Building RESTful APIs or web services
- Choosing appropriate HTTP framework for project requirements
- Implementing authentication or authorization middleware
- Designing REST endpoint patterns and validation
- Testing HTTP handlers and middleware chains
- Optimizing API performance and response times
- Migrating between HTTP frameworks

## Framework Selection Guide

### net/http (Standard Library) - Go 1.22+

**Use When:**
- Building simple to moderate complexity APIs
- Avoiding external dependencies is priority
- Need maximum compatibility and long-term stability
- Team prefers explicit over implicit patterns

**Strengths:**
- Zero dependencies, part of Go standard library
- Go 1.22+ pattern routing with path parameters
- Excellent performance and stability
- Extensive ecosystem compatibility
- No framework lock-in

**Limitations:**
- More verbose middleware composition
- Manual request validation
- No built-in binding or rendering

**Example:**
```go
package main

import (
    "encoding/json"
    "net/http"
    "log"
)

// Go 1.22+ pattern routing
func main() {
    mux := http.NewServeMux()

    // Path parameters with {param} syntax
    mux.HandleFunc("GET /users/{id}", getUserHandler)
    mux.HandleFunc("POST /users", createUserHandler)
    mux.HandleFunc("GET /users", listUsersHandler)

    // Middleware wrapping
    handler := loggingMiddleware(mux)

    log.Fatal(http.ListenAndServe(":8080", handler))
}

func getUserHandler(w http.ResponseWriter, r *http.Request) {
    id := r.PathValue("id") // Go 1.22+ path parameter extraction

    user := User{ID: id, Name: "John Doe"}

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

func loggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        log.Printf("%s %s", r.Method, r.URL.Path)
        next.ServeHTTP(w, r)
    })
}

type User struct {
    ID   string `json:"id"`
    Name string `json:"name"`
}
```

### Chi - Lightweight Router

**Use When:**
- Want stdlib-compatible router with better ergonomics
- Need clean middleware composition
- Prefer explicit over magic patterns
- Building moderate to complex routing structures

**Strengths:**
- 100% compatible with `net/http`
- Excellent middleware ecosystem
- Route grouping and nesting
- Context-based parameter passing
- Minimal performance overhead

**Installation:**
```bash
go get -u github.com/go-chi/chi/v5
```

**Example:**
```go
package main

import (
    "encoding/json"
    "net/http"
    "github.com/go-chi/chi/v5"
    "github.com/go-chi/chi/v5/middleware"
)

func main() {
    r := chi.NewRouter()

    // Built-in middleware
    r.Use(middleware.Logger)
    r.Use(middleware.Recoverer)
    r.Use(middleware.RequestID)

    // Route grouping
    r.Route("/api/v1", func(r chi.Router) {
        r.Route("/users", func(r chi.Router) {
            r.Get("/", listUsers)
            r.Post("/", createUser)

            r.Route("/{userID}", func(r chi.Router) {
                r.Use(UserContext) // Middleware for nested routes
                r.Get("/", getUser)
                r.Put("/", updateUser)
                r.Delete("/", deleteUser)
            })
        })
    })

    http.ListenAndServe(":8080", r)
}

func UserContext(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        userID := chi.URLParam(r, "userID")
        // Load user from database, set in context
        ctx := context.WithValue(r.Context(), "user", userID)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

func getUser(w http.ResponseWriter, r *http.Request) {
    userID := r.Context().Value("user").(string)
    // Return user data
    json.NewEncoder(w).Encode(map[string]string{"id": userID})
}
```

### Gin - High Performance Framework

**Use When:**
- Need maximum performance (8x faster than most frameworks)
- Want batteries-included experience
- Require built-in validation and binding
- Building JSON APIs with minimal boilerplate

**Strengths:**
- Extremely fast (fastest Go framework in benchmarks)
- Built-in JSON binding and validation
- Middleware ecosystem
- Group-based routing
- Custom error handling

**Installation:**
```bash
go get -u github.com/gin-gonic/gin
```

**Example:**
```go
package main

import (
    "net/http"
    "github.com/gin-gonic/gin"
)

type CreateUserRequest struct {
    Name  string `json:"name" binding:"required,min=3"`
    Email string `json:"email" binding:"required,email"`
    Age   int    `json:"age" binding:"required,gte=18"`
}

func main() {
    r := gin.Default() // Logger + Recovery middleware

    api := r.Group("/api/v1")
    {
        users := api.Group("/users")
        {
            users.GET("", listUsers)
            users.POST("", createUser)
            users.GET("/:id", getUser)
            users.PUT("/:id", updateUser)
            users.DELETE("/:id", deleteUser)
        }
    }

    r.Run(":8080")
}

func createUser(c *gin.Context) {
    var req CreateUserRequest

    // Automatic validation based on struct tags
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    // Process user creation
    user := User{
        ID:    generateID(),
        Name:  req.Name,
        Email: req.Email,
        Age:   req.Age,
    }

    c.JSON(http.StatusCreated, user)
}

func getUser(c *gin.Context) {
    id := c.Param("id")

    // Return user
    c.JSON(http.StatusOK, gin.H{
        "id":   id,
        "name": "John Doe",
    })
}
```

### Echo - Enterprise Framework

**Use When:**
- Building enterprise applications
- Need OpenAPI/Swagger integration
- Want comprehensive middleware library
- Require type-safe routing and binding

**Strengths:**
- Type-safe routing with automatic parameter binding
- Built-in middleware for common patterns
- OpenAPI/Swagger generation support
- Excellent error handling middleware
- WebSocket support

**Installation:**
```bash
go get -u github.com/labstack/echo/v4
```

**Example:**
```go
package main

import (
    "net/http"
    "github.com/labstack/echo/v4"
    "github.com/labstack/echo/v4/middleware"
)

func main() {
    e := echo.New()

    // Middleware
    e.Use(middleware.Logger())
    e.Use(middleware.Recover())
    e.Use(middleware.CORS())

    // Routes
    e.GET("/users/:id", getUser)
    e.POST("/users", createUser)
    e.PUT("/users/:id", updateUser)
    e.DELETE("/users/:id", deleteUser)

    e.Logger.Fatal(e.Start(":8080"))
}

func getUser(c echo.Context) error {
    id := c.Param("id")

    user := User{ID: id, Name: "John Doe"}
    return c.JSON(http.StatusOK, user)
}

func createUser(c echo.Context) error {
    var user User

    if err := c.Bind(&user); err != nil {
        return echo.NewHTTPError(http.StatusBadRequest, err.Error())
    }

    if err := c.Validate(&user); err != nil {
        return echo.NewHTTPError(http.StatusBadRequest, err.Error())
    }

    return c.JSON(http.StatusCreated, user)
}

// Custom error handler
func customErrorHandler(err error, c echo.Context) {
    code := http.StatusInternalServerError
    message := "Internal Server Error"

    if he, ok := err.(*echo.HTTPError); ok {
        code = he.Code
        message = he.Message.(string)
    }

    c.JSON(code, map[string]string{"error": message})
}
```

### Fiber - Express.js Style

**Use When:**
- Team familiar with Express.js patterns
- Need WebSocket support out of the box
- Building real-time applications
- Want fastest route matching performance

**Strengths:**
- Express.js-inspired API (easy for Node.js developers)
- Fastest route matching (uses fasthttp)
- Built-in WebSocket support
- Template engine support
- File upload handling

**Limitations:**
- Uses fasthttp instead of net/http (less ecosystem compatibility)
- Not compatible with standard http.Handler interface
- Slightly less mature ecosystem

**Installation:**
```bash
go get -u github.com/gofiber/fiber/v2
```

**Example:**
```go
package main

import (
    "github.com/gofiber/fiber/v2"
    "github.com/gofiber/fiber/v2/middleware/logger"
    "github.com/gofiber/fiber/v2/middleware/cors"
)

func main() {
    app := fiber.New()

    // Middleware
    app.Use(logger.New())
    app.Use(cors.New())

    // Routes
    api := app.Group("/api/v1")

    users := api.Group("/users")
    users.Get("/", listUsers)
    users.Post("/", createUser)
    users.Get("/:id", getUser)
    users.Put("/:id", updateUser)
    users.Delete("/:id", deleteUser)

    app.Listen(":8080")
}

func getUser(c *fiber.Ctx) error {
    id := c.Params("id")

    return c.JSON(fiber.Map{
        "id":   id,
        "name": "John Doe",
    })
}

func createUser(c *fiber.Ctx) error {
    var user User

    if err := c.BodyParser(&user); err != nil {
        return c.Status(400).JSON(fiber.Map{"error": err.Error()})
    }

    return c.Status(201).JSON(user)
}
```

## Common HTTP Patterns

### Request Validation

**Struct Tag Validation:**
```go
import "github.com/go-playground/validator/v10"

type CreateUserRequest struct {
    Name     string `json:"name" validate:"required,min=3,max=50"`
    Email    string `json:"email" validate:"required,email"`
    Age      int    `json:"age" validate:"required,gte=18,lte=120"`
    Password string `json:"password" validate:"required,min=8"`
}

var validate = validator.New()

func validateRequest(req interface{}) error {
    return validate.Struct(req)
}

// Usage
func createUser(w http.ResponseWriter, r *http.Request) {
    var req CreateUserRequest

    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
        http.Error(w, "Invalid JSON", http.StatusBadRequest)
        return
    }

    if err := validateRequest(&req); err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }

    // Process valid request
}
```

**Custom Validators:**
```go
import "github.com/go-playground/validator/v10"

var validate = validator.New()

func init() {
    validate.RegisterValidation("username", validateUsername)
}

func validateUsername(fl validator.FieldLevel) bool {
    username := fl.Field().String()
    // Custom validation logic
    return len(username) >= 3 && isAlphanumeric(username)
}

type SignupRequest struct {
    Username string `validate:"required,username"`
    Email    string `validate:"required,email"`
}
```

### Middleware Patterns

**Authentication Middleware:**
```go
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
        }

        // Validate token
        userID, err := validateToken(token)
        if err != nil {
            http.Error(w, "Invalid token", http.StatusUnauthorized)
            return
        }

        // Add user to context
        ctx := context.WithValue(r.Context(), "userID", userID)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}
```

**Rate Limiting Middleware:**
```go
import (
    "golang.org/x/time/rate"
    "sync"
)

type RateLimiter struct {
    limiters map[string]*rate.Limiter
    mu       sync.RWMutex
    rate     rate.Limit
    burst    int
}

func NewRateLimiter(r rate.Limit, b int) *RateLimiter {
    return &RateLimiter{
        limiters: make(map[string]*rate.Limiter),
        rate:     r,
        burst:    b,
    }
}

func (rl *RateLimiter) getLimiter(ip string) *rate.Limiter {
    rl.mu.Lock()
    defer rl.mu.Unlock()

    limiter, exists := rl.limiters[ip]
    if !exists {
        limiter = rate.NewLimiter(rl.rate, rl.burst)
        rl.limiters[ip] = limiter
    }

    return limiter
}

func (rl *RateLimiter) Middleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        ip := r.RemoteAddr
        limiter := rl.getLimiter(ip)

        if !limiter.Allow() {
            http.Error(w, "Rate limit exceeded", http.StatusTooManyRequests)
            return
        }

        next.ServeHTTP(w, r)
    })
}
```

**CORS Middleware:**
```go
func CORSMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Access-Control-Allow-Origin", "*")
        w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
        w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")

        if r.Method == "OPTIONS" {
            w.WriteHeader(http.StatusOK)
            return
        }

        next.ServeHTTP(w, r)
    })
}
```

### Error Handling Strategies

**Custom Error Types:**
```go
type APIError struct {
    Code    int    `json:"code"`
    Message string `json:"message"`
    Details string `json:"details,omitempty"`
}

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

// Error constructors
func NewBadRequestError(msg string) *APIError {
    return &APIError{Code: http.StatusBadRequest, Message: msg}
}

func NewNotFoundError(msg string) *APIError {
    return &APIError{Code: http.StatusNotFound, Message: msg}
}

func NewInternalError(msg string) *APIError {
    return &APIError{Code: http.StatusInternalServerError, Message: msg}
}
```

**Error Response Middleware:**
```go
type APIHandler func(w http.ResponseWriter, r *http.Request) error

func ErrorHandlerMiddleware(h APIHandler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        err := h(w, r)
        if err == nil {
            return
        }

        // Handle different error types
        var apiErr *APIError
        if errors.As(err, &apiErr) {
            w.Header().Set("Content-Type", "application/json")
            w.WriteHeader(apiErr.Code)
            json.NewEncoder(w).Encode(apiErr)
            return
        }

        // Unknown error - log and return generic message
        log.Printf("Internal error: %v", err)
        w.WriteHeader(http.StatusInternalServerError)
        json.NewEncoder(w).Encode(APIError{
            Code:    http.StatusInternalServerError,
            Message: "Internal server error",
        })
    })
}

// Usage
func getUserHandler(w http.ResponseWriter, r *http.Request) error {
    id := r.PathValue("id")

    user, err := db.GetUser(id)
    if err != nil {
        if errors.Is(err, sql.ErrNoRows) {
            return NewNotFoundError("User not found")
        }
        return fmt.Errorf("database error: %w", err)
    }

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

// Register with middleware
mux.Handle("GET /users/{id}", ErrorHandlerMiddleware(getUserHandler))
```

### REST API Design Patterns

**Resource Naming Conventions:**
```go
// Good: Plural nouns for collections
GET    /api/v1/users           // List users
POST   /api/v1/users           // Create user
GET    /api/v1/users/{id}      // Get user
PUT    /api/v1/users/{id}      // Update user (full)
PATCH  /api/v1/users/{id}      // Update user (partial)
DELETE /api/v1/users/{id}      // Delete user

// Nested resources
GET    /api/v1/users/{id}/posts        // User's posts
POST   /api/v1/users/{id}/posts        // Create post for user
GET    /api/v1/users/{id}/posts/{pid}  // Specific post

// Avoid: Verbs in URLs (use HTTP methods instead)
// Bad: POST /api/v1/users/create
// Bad: GET  /api/v1/users/get/{id}
```

**Pagination Pattern:**
```go
type PaginationParams struct {
    Page     int `json:"page" validate:"gte=1"`
    PageSize int `json:"page_size" validate:"gte=1,lte=100"`
}

type PaginatedResponse struct {
    Data       interface{} `json:"data"`
    Page       int         `json:"page"`
    PageSize   int         `json:"page_size"`
    TotalCount int         `json:"total_count"`
    TotalPages int         `json:"total_pages"`
}

func listUsers(w http.ResponseWriter, r *http.Request) {
    // Parse pagination params
    page, _ := strconv.Atoi(r.URL.Query().Get("page"))
    if page < 1 {
        page = 1
    }

    pageSize, _ := strconv.Atoi(r.URL.Query().Get("page_size"))
    if pageSize < 1 || pageSize > 100 {
        pageSize = 20
    }

    // Fetch data
    users, totalCount := db.GetUsers(page, pageSize)
    totalPages := (totalCount + pageSize - 1) / pageSize

    response := PaginatedResponse{
        Data:       users,
        Page:       page,
        PageSize:   pageSize,
        TotalCount: totalCount,
        TotalPages: totalPages,
    }

    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(response)
}
```

**Query Parameter Filtering:**
```go
type UserFilter struct {
    Status    string `json:"status"`
    Role      string `json:"role"`
    CreatedAt string `json:"created_at"`
    Search    string `json:"search"`
}

func parseFilters(r *http.Request) UserFilter {
    return UserFilter{
        Status:    r.URL.Query().Get("status"),
        Role:      r.URL.Query().Get("role"),
        CreatedAt: r.URL.Query().Get("created_at"),
        Search:    r.URL.Query().Get("search"),
    }
}

func listUsers(w http.ResponseWriter, r *http.Request) {
    filters := parseFilters(r)

    users := db.GetUsersWithFilters(filters)

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

// Example request: GET /api/v1/users?status=active&role=admin&search=john
```

### HTTP Client Patterns

**Production-Ready HTTP Client:**
```go
import (
    "context"
    "net/http"
    "time"
)

func NewHTTPClient() *http.Client {
    return &http.Client{
        Timeout: 30 * time.Second,
        Transport: &http.Transport{
            MaxIdleConns:        100,
            MaxIdleConnsPerHost: 10,
            IdleConnTimeout:     90 * time.Second,
            DisableKeepAlives:   false,
        },
    }
}

// Making requests with context
func fetchUser(ctx context.Context, userID string) (*User, error) {
    client := NewHTTPClient()

    req, err := http.NewRequestWithContext(
        ctx,
        "GET",
        fmt.Sprintf("https://api.example.com/users/%s", userID),
        nil,
    )
    if err != nil {
        return nil, fmt.Errorf("create request: %w", err)
    }

    req.Header.Set("Accept", "application/json")
    req.Header.Set("Authorization", "Bearer "+getToken())

    resp, err := client.Do(req)
    if err != nil {
        return nil, fmt.Errorf("execute request: %w", err)
    }
    defer resp.Body.Close()

    if resp.StatusCode != http.StatusOK {
        return nil, fmt.Errorf("unexpected status: %d", resp.StatusCode)
    }

    var user User
    if err := json.NewDecoder(resp.Body).Decode(&user); err != nil {
        return nil, fmt.Errorf("decode response: %w", err)
    }

    return &user, nil
}
```

**Retry Logic with Exponential Backoff:**
```go
import (
    "context"
    "math"
    "time"
)

type RetryConfig struct {
    MaxRetries int
    BaseDelay  time.Duration
    MaxDelay   time.Duration
}

func DoWithRetry(ctx context.Context, cfg RetryConfig, fn func() error) error {
    var err error

    for attempt := 0; attempt <= cfg.MaxRetries; attempt++ {
        err = fn()
        if err == nil {
            return nil
        }

        if attempt < cfg.MaxRetries {
            // Exponential backoff
            delay := time.Duration(math.Pow(2, float64(attempt))) * cfg.BaseDelay
            if delay > cfg.MaxDelay {
                delay = cfg.MaxDelay
            }

            select {
            case <-ctx.Done():
                return ctx.Err()
            case <-time.After(delay):
                // Continue to next attempt
            }
        }
    }

    return fmt.Errorf("max retries exceeded: %w", err)
}

// Usage
err := DoWithRetry(ctx, RetryConfig{
    MaxRetries: 3,
    BaseDelay:  100 * time.Millisecond,
    MaxDelay:   2 * time.Second,
}, func() error {
    return makeAPIRequest()
})
```

## Testing HTTP Handlers

**Using httptest.Server:**
```go
import (
    "net/http"
    "net/http/httptest"
    "testing"
)

func TestGetUser(t *testing.T) {
    // Create test server
    ts := httptest.NewServer(http.HandlerFunc(getUserHandler))
    defer ts.Close()

    // Make request
    resp, err := http.Get(ts.URL + "/users/123")
    if err != nil {
        t.Fatal(err)
    }
    defer resp.Body.Close()

    // Assertions
    if resp.StatusCode != http.StatusOK {
        t.Errorf("expected status 200, got %d", resp.StatusCode)
    }

    var user User
    if err := json.NewDecoder(resp.Body).Decode(&user); err != nil {
        t.Fatal(err)
    }

    if user.ID != "123" {
        t.Errorf("expected user ID 123, got %s", user.ID)
    }
}
```

**Testing Middleware:**
```go
func TestAuthMiddleware(t *testing.T) {
    handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        userID := r.Context().Value("userID")
        if userID == nil {
            t.Error("userID not found in context")
        }
        w.WriteHeader(http.StatusOK)
    })

    wrapped := AuthMiddleware(handler)

    tests := []struct {
        name       string
        token      string
        wantStatus int
    }{
        {"Valid token", "valid-token-123", http.StatusOK},
        {"Missing token", "", http.StatusUnauthorized},
        {"Invalid token", "invalid", http.StatusUnauthorized},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            req := httptest.NewRequest("GET", "/test", nil)
            if tt.token != "" {
                req.Header.Set("Authorization", tt.token)
            }

            rr := httptest.NewRecorder()
            wrapped.ServeHTTP(rr, req)

            if rr.Code != tt.wantStatus {
                t.Errorf("expected status %d, got %d", tt.wantStatus, rr.Code)
            }
        })
    }
}
```

## Performance Optimization

**Response Compression:**
```go
import (
    "compress/gzip"
    "io"
    "net/http"
    "strings"
)

type gzipResponseWriter struct {
    io.Writer
    http.ResponseWriter
}

func (w gzipResponseWriter) Write(b []byte) (int, error) {
    return w.Writer.Write(b)
}

func GzipMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
            next.ServeHTTP(w, r)
            return
        }

        w.Header().Set("Content-Encoding", "gzip")
        gz := gzip.NewWriter(w)
        defer gz.Close()

        gzw := gzipResponseWriter{Writer: gz, ResponseWriter: w}
        next.ServeHTTP(gzw, r)
    })
}
```

**Connection Pooling Configuration:**
```go
import (
    "net"
    "net/http"
    "time"
)

func NewProductionHTTPClient() *http.Client {
    return &http.Client{
        Timeout: 30 * time.Second,
        Transport: &http.Transport{
            Proxy: http.ProxyFromEnvironment,
            DialContext: (&net.Dialer{
                Timeout:   30 * time.Second,
                KeepAlive: 30 * time.Second,
            }).DialContext,
            ForceAttemptHTTP2:     true,
            MaxIdleConns:          100,
            MaxIdleConnsPerHost:   10,
            IdleConnTimeout:       90 * time.Second,
            TLSHandshakeTimeout:   10 * time.Second,
            ExpectContinueTimeout: 1 * time.Second,
        },
    }
}
```

## Decision Tree

```
Building HTTP API in Go?
│
├─ Simple API, few dependencies? → net/http (stdlib)
│  ├─ Go 1.22+? → Use new ServeMux pattern routing
│  └─ Go < 1.22? → Consider Chi for better routing
│
├─ Need stdlib compatibility + better ergonomics? → Chi
│  └─ Great for: Middleware chains, route grouping
│
├─ Maximum performance priority? → Gin
│  └─ Great for: JSON APIs, high throughput services
│
├─ Enterprise app with OpenAPI? → Echo
│  └─ Great for: Type safety, comprehensive middleware
│
└─ Team knows Express.js + need WebSockets? → Fiber
   └─ Note: Uses fasthttp, not stdlib-compatible
```

## Common Pitfalls

**Pitfall 1: Not Closing Response Bodies**
```go
// Bad: Memory leak
resp, _ := http.Get(url)
body, _ := io.ReadAll(resp.Body) // Never closed!

// Good: Always defer close
resp, err := http.Get(url)
if err != nil {
    return err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
```

**Pitfall 2: Not Using Context for Timeouts**
```go
// Bad: No timeout control
resp, _ := http.Get(url)

// Good: Use context with timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
resp, err := client.Do(req)
```

**Pitfall 3: Ignoring HTTP Status Codes**
```go
// Bad: Not checking status
resp, _ := http.Get(url)
defer resp.Body.Close()
json.NewDecoder(resp.Body).Decode(&result)

// Good: Always check status
resp, err := http.Get(url)
if err != nil {
    return err
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
    return fmt.Errorf("unexpected status: %d", resp.StatusCode)
}
```

**Pitfall 4: Not Reusing HTTP Clients**
```go
// Bad: Creates new client per request (no connection pooling)
func makeRequest() {
    client := &http.Client{}
    resp, _ := client.Get(url)
}

// Good: Reuse client for connection pooling
var httpClient = &http.Client{
    Timeout: 30 * time.Second,
}

func makeRequest() {
    resp, _ := httpClient.Get(url)
}
```

## Go Idiomatic Quality Rules

Small, high-frequency Go idioms worth enforcing in handler/service code:

- **Use `:=` for locals with an initial value** — reserve `var` for zero values and
  package-level declarations.
- **Don't name an unused method receiver** — `func (*Server) Foo()` makes it clear the
  receiver is irrelevant.
- **Initialize struct pointers with `&T{}`, not `new(T)`** — consistent with composite
  literals and lets you set fields inline.
- **Handle (don't discard) returned errors** — a dropped error from `Decode`/`Write`/a
  repository call turns a failure into a silent success.

See **[go-idioms-quality.md](references/go-idioms-quality.md)** for non-idiomatic vs
idiomatic examples and how to test/lint each. Most are enforced by `gofmt -s`,
`staticcheck`, `errcheck`, and `revive` under `golangci-lint`.

> Derived from CAST Highlight Go-tagged code-quality indicators
> (https://doc.casthighlight.com/), which reference Effective Go and the Go Code Review
> Comments wiki as primary sources.

## Related Skills

- **golang-testing-strategies**: Testing HTTP handlers and middleware
- **golang-database-patterns**: Integrating databases with HTTP APIs
- **toolchains-typescript-frameworks-nodejs-backend**: Comparison with Node.js patterns

## References

- [Go net/http Documentation](https://pkg.go.dev/net/http)
- [Chi Framework](https://go-chi.io/)
- [Gin Framework](https://gin-gonic.com/)
- [Echo Framework](https://echo.labstack.com/)
- [Fiber Framework](https://gofiber.io/)
- [Go HTTP Best Practices](https://golang.org/doc/effective_go#web_server)

© Claude MPM Team · MIT · bobmatnyc/claude-mpm-skills

Similar skills

All skills for Backend and APIs →

From the same set125

golang-testing-strategies A skill for writing Go tests: table-driven cases, testify assertions, gomock mocks, benchmarks and CI coverage. Useful when a project's test suite needs structure. CX Codex +1 443installs sveltekit A skill that tells the agent how SvelteKit works: file-based routing, load functions, form actions and deployment adapters. CX Codex +1 441installs wordpress-advanced-architecture A skill for advanced WordPress work: custom REST routes, WP-CLI commands, transient and object caching. CX Codex +1 441installs webapp-testing A Playwright testing reference for coding agents. It provides selector hierarchies, wait strategies, and form interaction patterns. CX Codex +1 441installs digitalocean-management A skill for Claude, Codex and Cursor that walks through monitoring, uptime checks and Projects on DigitalOcean. Useful when you need alerts and operational visibility for droplets and clusters. CX Codex +1 438installs flask A skill that hands your agent ready Flask patterns: app factory, blueprints, request and response handling. Useful when you're building a REST API or microservice in Python. CX Codex +1 437installs hono-rpc A skill that walks through Hono RPC: export AppType on the server, call hc<AppType> on the client, skip codegen entirely. CX Codex +1 437installs hono-middleware A skill covering Hono middleware: writing your own, composing them, the built-in modules and the order they run in. Useful when a Hono app needs auth, CORS or logging. CX Codex +1 431installs

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor