> ## Documentation Index
> Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt
> Use this file to discover all available pages before exploring further.

# HTTP & Web Development

> Build production-ready APIs with net/http, routing, middleware, and popular frameworks

# HTTP & Web Development in Go

Go's `net/http` package is powerful enough for production use without external dependencies. This sets Go apart from most languages where you need a framework (Express, Flask, Spring) for anything beyond a toy server. Companies like Cloudflare, Dropbox, and Netflix run production services using just the standard library's HTTP server, sometimes with a lightweight router on top. This chapter covers building robust web services from the ground up.

## The net/http Package

### Basic HTTP Server

```go theme={null}
package main

import (
    "fmt"
    "log"
    "net/http"
)

func main() {
    http.HandleFunc("/", homeHandler)
    http.HandleFunc("/api/users", usersHandler)
    
    log.Println("Server starting on :8080")
    // ListenAndServe blocks forever; log.Fatal prints the error and exits
    // if the server fails to start (port in use, permissions, etc.)
    log.Fatal(http.ListenAndServe(":8080", nil))
}

func homeHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Welcome to the API!")
}

func usersHandler(w http.ResponseWriter, r *http.Request) {
    switch r.Method {
    case http.MethodGet:
        getUsers(w, r)
    case http.MethodPost:
        createUser(w, r)
    default:
        http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
    }
}
```

### http.Handler Interface

```go theme={null}
type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}
```

Any type that implements `ServeHTTP` can handle HTTP requests:

```go theme={null}
type APIHandler struct {
    db *sql.DB
}

func (h *APIHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    // Handle request with access to h.db
}

func main() {
    handler := &APIHandler{db: connectDB()}
    http.Handle("/api/", handler)
    http.ListenAndServe(":8080", nil)
}
```

***

## JSON APIs

### Encoding JSON Responses

```go theme={null}
type User struct {
    ID        int       `json:"id"`
    Name      string    `json:"name"`
    Email     string    `json:"email"`
    CreatedAt time.Time `json:"created_at"`
}

type Response struct {
    Data    interface{} `json:"data,omitempty"`
    Error   string      `json:"error,omitempty"`
    Message string      `json:"message,omitempty"`
}

func respondJSON(w http.ResponseWriter, status int, data interface{}) {
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(status)
    
    if err := json.NewEncoder(w).Encode(data); err != nil {
        log.Printf("Error encoding response: %v", err)
    }
}

func respondError(w http.ResponseWriter, status int, message string) {
    respondJSON(w, status, Response{Error: message})
}

func getUserHandler(w http.ResponseWriter, r *http.Request) {
    user := User{
        ID:        1,
        Name:      "John Doe",
        Email:     "john@example.com",
        CreatedAt: time.Now(),
    }
    
    respondJSON(w, http.StatusOK, Response{Data: user})
}
```

### Decoding JSON Requests

```go theme={null}
type CreateUserRequest struct {
    Name     string `json:"name" validate:"required,min=2,max=100"`
    Email    string `json:"email" validate:"required,email"`
    Password string `json:"password" validate:"required,min=8"`
}

func createUserHandler(w http.ResponseWriter, r *http.Request) {
    // Limit request body size
    r.Body = http.MaxBytesReader(w, r.Body, 1<<20) // 1MB
    
    var req CreateUserRequest
    decoder := json.NewDecoder(r.Body)
    decoder.DisallowUnknownFields() // Strict parsing
    
    if err := decoder.Decode(&req); err != nil {
        respondError(w, http.StatusBadRequest, "Invalid JSON: "+err.Error())
        return
    }
    
    // Validate (using validator package)
    if err := validate.Struct(req); err != nil {
        respondError(w, http.StatusBadRequest, "Validation failed: "+err.Error())
        return
    }
    
    // Create user...
    user := createUser(req)
    respondJSON(w, http.StatusCreated, Response{Data: user})
}
```

***

## Routing

### Standard Library Router (Go 1.22+)

Go 1.22 introduced enhanced routing patterns:

```go theme={null}
func main() {
    mux := http.NewServeMux()
    
    // Method-specific routing (Go 1.22+)
    mux.HandleFunc("GET /api/users", listUsers)
    mux.HandleFunc("POST /api/users", createUser)
    mux.HandleFunc("GET /api/users/{id}", getUser)
    mux.HandleFunc("PUT /api/users/{id}", updateUser)
    mux.HandleFunc("DELETE /api/users/{id}", deleteUser)
    
    // Wildcard patterns
    mux.HandleFunc("GET /files/{path...}", serveFiles)
    
    http.ListenAndServe(":8080", mux)
}

func getUser(w http.ResponseWriter, r *http.Request) {
    // Extract path parameter (Go 1.22+)
    id := r.PathValue("id")
    
    // Or manually for older Go versions
    // id := strings.TrimPrefix(r.URL.Path, "/api/users/")
    
    user, err := findUser(id)
    if err != nil {
        respondError(w, http.StatusNotFound, "User not found")
        return
    }
    
    respondJSON(w, http.StatusOK, Response{Data: user})
}
```

### Custom Router

```go theme={null}
type Router struct {
    routes map[string]map[string]http.HandlerFunc
}

func NewRouter() *Router {
    return &Router{
        routes: make(map[string]map[string]http.HandlerFunc),
    }
}

func (router *Router) Handle(method, path string, handler http.HandlerFunc) {
    if router.routes[path] == nil {
        router.routes[path] = make(map[string]http.HandlerFunc)
    }
    router.routes[path][method] = handler
}

func (router *Router) GET(path string, handler http.HandlerFunc) {
    router.Handle(http.MethodGet, path, handler)
}

func (router *Router) POST(path string, handler http.HandlerFunc) {
    router.Handle(http.MethodPost, path, handler)
}

func (router *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    if methods, ok := router.routes[r.URL.Path]; ok {
        if handler, ok := methods[r.Method]; ok {
            handler(w, r)
            return
        }
        http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
        return
    }
    http.NotFound(w, r)
}
```

***

## Middleware

Middleware are functions that wrap handlers to add functionality. Think of middleware as layers in an onion: each request passes through the outer layers (logging, auth, CORS) before reaching the core handler, and the response passes back out through the same layers. This is the Go version of the decorator pattern, and it is the standard way to add cross-cutting concerns like authentication, logging, rate limiting, and panic recovery.

### Middleware Pattern

```go theme={null}
type Middleware func(http.Handler) http.Handler

func Chain(h http.Handler, middlewares ...Middleware) http.Handler {
    for i := len(middlewares) - 1; i >= 0; i-- {
        h = middlewares[i](h)
    }
    return h
}
```

### Logging Middleware

```go theme={null}
func LoggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        
        // Wrap ResponseWriter to capture status code
        wrapped := &responseWriter{ResponseWriter: w, statusCode: http.StatusOK}
        
        next.ServeHTTP(wrapped, r)
        
        log.Printf(
            "%s %s %d %s",
            r.Method,
            r.URL.Path,
            wrapped.statusCode,
            time.Since(start),
        )
    })
}

type responseWriter struct {
    http.ResponseWriter
    statusCode int
}

func (rw *responseWriter) WriteHeader(code int) {
    rw.statusCode = code
    rw.ResponseWriter.WriteHeader(code)
}
```

### Recovery Middleware

```go theme={null}
func RecoveryMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        defer func() {
            if err := recover(); err != nil {
                log.Printf("Panic recovered: %v\n%s", err, debug.Stack())
                http.Error(w, "Internal Server Error", http.StatusInternalServerError)
            }
        }()
        
        next.ServeHTTP(w, r)
    })
}
```

### CORS Middleware

```go theme={null}
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 == http.MethodOptions {
            w.WriteHeader(http.StatusOK)
            return
        }
        
        next.ServeHTTP(w, r)
    })
}
```

### Authentication Middleware

```go theme={null}
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
        claims, err := validateToken(strings.TrimPrefix(token, "Bearer "))
        if err != nil {
            http.Error(w, "Invalid token", http.StatusUnauthorized)
            return
        }
        
        // Add claims to context
        ctx := context.WithValue(r.Context(), userClaimsKey{}, claims)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}
```

### Rate Limiting Middleware

```go theme={null}
func RateLimitMiddleware(rps int) Middleware {
    limiter := rate.NewLimiter(rate.Limit(rps), rps)
    
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            if !limiter.Allow() {
                http.Error(w, "Too Many Requests", http.StatusTooManyRequests)
                return
            }
            next.ServeHTTP(w, r)
        })
    }
}
```

### Using Middleware

```go theme={null}
func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/api/users", usersHandler)
    mux.HandleFunc("/api/orders", ordersHandler)
    
    // Apply middleware chain
    handler := Chain(
        mux,
        RecoveryMiddleware,
        LoggingMiddleware,
        CORSMiddleware,
        RateLimitMiddleware(100),
    )
    
    http.ListenAndServe(":8080", handler)
}
```

***

## HTTP Server Configuration

### Production-Ready Server

```go theme={null}
func main() {
    mux := setupRoutes()
    
    srv := &http.Server{
        Addr:         ":8080",
        Handler:      mux,
        ReadTimeout:  5 * time.Second,
        WriteTimeout: 10 * time.Second,
        IdleTimeout:  120 * time.Second,
        MaxHeaderBytes: 1 << 20, // 1MB
    }
    
    // Graceful shutdown
    go func() {
        sigChan := make(chan os.Signal, 1)
        signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
        <-sigChan
        
        ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
        defer cancel()
        
        log.Println("Shutting down server...")
        if err := srv.Shutdown(ctx); err != nil {
            log.Printf("Server shutdown error: %v", err)
        }
    }()
    
    log.Printf("Server starting on %s", srv.Addr)
    if err := srv.ListenAndServe(); err != http.ErrServerClosed {
        log.Fatal(err)
    }
    
    log.Println("Server stopped")
}
```

### TLS Configuration

```go theme={null}
func main() {
    tlsConfig := &tls.Config{
        MinVersion: tls.VersionTLS12,
        CipherSuites: []uint16{
            tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
            tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
        },
    }
    
    srv := &http.Server{
        Addr:      ":443",
        Handler:   mux,
        TLSConfig: tlsConfig,
    }
    
    log.Fatal(srv.ListenAndServeTLS("cert.pem", "key.pem"))
}
```

***

## HTTP Client

### Basic Client

<Warning>
  **Pitfall -- Using http.DefaultClient in Production**: The default `http.Get`, `http.Post`, and `http.DefaultClient` have no timeout. A slow or unresponsive server will cause your goroutine to hang indefinitely, eventually exhausting resources. Always create a custom client with explicit timeouts:

  ```go theme={null}
  // WRONG: no timeout, hangs forever on slow servers
  resp, err := http.Get("https://api.example.com/users/1")

  // RIGHT: always set timeouts
  client := &http.Client{Timeout: 10 * time.Second}
  resp, err := client.Get("https://api.example.com/users/1")
  ```
</Warning>

```go theme={null}
func fetchUser(id string) (*User, error) {
    resp, err := http.Get("https://api.example.com/users/" + id)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close() // Always close: returns connection to pool for reuse
    
    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, err
    }
    
    return &user, nil
}
```

### Production HTTP Client

A production HTTP client is like a well-tuned car engine: it needs proper connection pooling (how many connections to keep warm), timeouts (when to give up), and transport settings (how to manage the underlying TCP connections). The `http.Transport` is the engine; the `http.Client` is the driver interface.

```go theme={null}
type APIClient struct {
    baseURL    string
    httpClient *http.Client
    apiKey     string
}

func NewAPIClient(baseURL, apiKey string) *APIClient {
    return &APIClient{
        baseURL: baseURL,
        apiKey:  apiKey,
        httpClient: &http.Client{
            Timeout: 30 * time.Second, // Overall request timeout
            Transport: &http.Transport{
                MaxIdleConns:        100, // Total idle connections across all hosts
                MaxIdleConnsPerHost: 10,  // Idle connections per host (tune for your traffic)
                IdleConnTimeout:     90 * time.Second, // How long idle connections survive
            },
        },
    }
}

func (c *APIClient) do(ctx context.Context, method, path string, body interface{}) (*http.Response, error) {
    var bodyReader io.Reader
    if body != nil {
        jsonBody, err := json.Marshal(body)
        if err != nil {
            return nil, err
        }
        bodyReader = bytes.NewReader(jsonBody)
    }
    
    req, err := http.NewRequestWithContext(ctx, method, c.baseURL+path, bodyReader)
    if err != nil {
        return nil, err
    }
    
    req.Header.Set("Content-Type", "application/json")
    req.Header.Set("Authorization", "Bearer "+c.apiKey)
    req.Header.Set("User-Agent", "MyApp/1.0")
    
    return c.httpClient.Do(req)
}

func (c *APIClient) GetUser(ctx context.Context, id string) (*User, error) {
    resp, err := c.do(ctx, http.MethodGet, "/users/"+id, nil)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()
    
    if resp.StatusCode == http.StatusNotFound {
        return nil, ErrNotFound
    }
    
    if resp.StatusCode != http.StatusOK {
        body, _ := io.ReadAll(resp.Body)
        return nil, fmt.Errorf("API error: %d - %s", resp.StatusCode, string(body))
    }
    
    var user User
    if err := json.NewDecoder(resp.Body).Decode(&user); err != nil {
        return nil, err
    }
    
    return &user, nil
}

func (c *APIClient) CreateUser(ctx context.Context, user *CreateUserRequest) (*User, error) {
    resp, err := c.do(ctx, http.MethodPost, "/users", user)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()
    
    if resp.StatusCode != http.StatusCreated {
        return nil, parseAPIError(resp)
    }
    
    var created User
    if err := json.NewDecoder(resp.Body).Decode(&created); err != nil {
        return nil, err
    }
    
    return &created, nil
}
```

### Retry with Exponential Backoff

```go theme={null}
func (c *APIClient) doWithRetry(ctx context.Context, method, path string, body interface{}) (*http.Response, error) {
    maxRetries := 3
    baseDelay := 100 * time.Millisecond
    
    var lastErr error
    for attempt := 0; attempt < maxRetries; attempt++ {
        if attempt > 0 {
            delay := baseDelay * time.Duration(1<<uint(attempt-1))
            select {
            case <-ctx.Done():
                return nil, ctx.Err()
            case <-time.After(delay):
            }
        }
        
        resp, err := c.do(ctx, method, path, body)
        if err != nil {
            lastErr = err
            continue
        }
        
        // Don't retry on client errors
        if resp.StatusCode >= 400 && resp.StatusCode < 500 {
            return resp, nil
        }
        
        // Retry on server errors
        if resp.StatusCode >= 500 {
            resp.Body.Close()
            lastErr = fmt.Errorf("server error: %d", resp.StatusCode)
            continue
        }
        
        return resp, nil
    }
    
    return nil, fmt.Errorf("max retries exceeded: %w", lastErr)
}
```

***

## Popular Frameworks

### Chi Router

```go theme={null}
import "github.com/go-chi/chi/v5"

func main() {
    r := chi.NewRouter()
    
    // Built-in middleware
    r.Use(middleware.Logger)
    r.Use(middleware.Recoverer)
    r.Use(middleware.Timeout(60 * time.Second))
    
    // Routes
    r.Get("/", homeHandler)
    
    // Route groups
    r.Route("/api/v1", func(r chi.Router) {
        r.Use(AuthMiddleware)
        
        r.Route("/users", func(r chi.Router) {
            r.Get("/", listUsers)
            r.Post("/", createUser)
            r.Get("/{id}", getUser)
            r.Put("/{id}", updateUser)
            r.Delete("/{id}", deleteUser)
        })
    })
    
    http.ListenAndServe(":8080", r)
}

func getUser(w http.ResponseWriter, r *http.Request) {
    id := chi.URLParam(r, "id")
    // ...
}
```

### Gin Framework

```go theme={null}
import "github.com/gin-gonic/gin"

func main() {
    r := gin.Default() // Includes logger and recovery
    
    // Middleware
    r.Use(CORSMiddleware())
    
    // Routes
    api := r.Group("/api/v1")
    {
        api.Use(AuthMiddleware())
        
        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 getUser(c *gin.Context) {
    id := c.Param("id")
    
    user, err := findUser(id)
    if err != nil {
        c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
        return
    }
    
    c.JSON(http.StatusOK, user)
}

func createUser(c *gin.Context) {
    var req CreateUserRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }
    
    user := createUserFromRequest(req)
    c.JSON(http.StatusCreated, user)
}
```

### Echo Framework

```go theme={null}
import "github.com/labstack/echo/v4"

func main() {
    e := echo.New()
    
    // Middleware
    e.Use(middleware.Logger())
    e.Use(middleware.Recover())
    
    // Routes
    api := e.Group("/api/v1")
    api.Use(AuthMiddleware)
    
    api.GET("/users", listUsers)
    api.POST("/users", createUser)
    api.GET("/users/:id", getUser)
    
    e.Logger.Fatal(e.Start(":8080"))
}

func getUser(c echo.Context) error {
    id := c.Param("id")
    
    user, err := findUser(id)
    if err != nil {
        return c.JSON(http.StatusNotFound, map[string]string{"error": "not found"})
    }
    
    return c.JSON(http.StatusOK, user)
}
```

***

## Interview Questions

<AccordionGroup>
  <Accordion title="What's the difference between http.Handle and http.HandleFunc?">
    * `http.Handle` takes an `http.Handler` interface
    * `http.HandleFunc` takes a function with signature `func(w http.ResponseWriter, r *http.Request)`

    `HandleFunc` is a convenience wrapper that converts the function to a `HandlerFunc` type which implements `Handler`.
  </Accordion>

  <Accordion title="How would you implement request timeouts?">
    Multiple approaches:

    1. Server-level: Set `ReadTimeout`, `WriteTimeout` on `http.Server`
    2. Request-level: Use `context.WithTimeout` with `http.NewRequestWithContext`
    3. Handler-level: Use `http.TimeoutHandler` wrapper
    4. Middleware: Create custom timeout middleware
  </Accordion>

  <Accordion title="What's the purpose of defer resp.Body.Close()?">
    HTTP responses must have their body closed to:

    * Release the connection back to the pool for reuse
    * Prevent resource leaks (file descriptors, memory)
    * Allow the transport to reuse connections (keep-alive)
  </Accordion>

  <Accordion title="How do you prevent slow clients from holding connections?">
    * Set appropriate server timeouts (`ReadTimeout`, `WriteTimeout`, `IdleTimeout`)
    * Use `http.MaxBytesReader` to limit request body size
    * Implement request deadlines with context
    * Use rate limiting middleware
  </Accordion>
</AccordionGroup>

***

## Summary

| Topic      | Key Points                                         |
| ---------- | -------------------------------------------------- |
| net/http   | Built-in, production-ready, Handler interface      |
| Routing    | Go 1.22+ patterns, or use Chi/Gin/Echo             |
| Middleware | Chain pattern, logging, auth, recovery             |
| JSON       | Encoder/Decoder, validation, proper error handling |
| Client     | Timeouts, connection pooling, retry logic          |
| Security   | TLS, rate limiting, input validation               |

<Warning>
  **Pitfall -- Not Draining Response Bodies**: If you read only part of an HTTP response body (or skip reading it entirely) without closing it, the underlying TCP connection cannot be reused. Always drain and close the body, even for error responses:

  ```go theme={null}
  resp, err := client.Do(req)
  if err != nil {
      return err
  }
  defer func() {
      // Drain any unread body so the connection can be reused
      io.Copy(io.Discard, resp.Body)
      resp.Body.Close()
  }()
  ```
</Warning>

<Warning>
  **Pitfall -- Goroutine Leak in HTTP Handlers**: If your handler spawns a goroutine that outlives the request (e.g., for background processing) and that goroutine holds a reference to the request context or response writer, you get undefined behavior. The `ResponseWriter` is invalid after `ServeHTTP` returns. If you need background work, copy the data you need and use a fresh `context.Background()`.
</Warning>

***

## Interview Deep-Dive

<AccordionGroup>
  <Accordion title="Go's net/http package is considered production-ready without frameworks. What specifically makes it sufficient, and when would you reach for Chi, Gin, or Echo instead?">
    **Strong Answer:**

    * The net/http standard library provides: a production-grade HTTP server with configurable timeouts, keep-alive, TLS, and graceful shutdown; the `http.Handler` interface (one method: `ServeHTTP`) which is the foundation of Go's composable middleware ecosystem; built-in connection pooling in the HTTP client; and since Go 1.22, method-based routing with path parameters (`GET /users/{id}`).
    * What it lacks: Go 1.22 routing covers most cases, but before that, the default mux only did prefix matching without method dispatch, which was insufficient for REST APIs. Other missing pieces: route groups with shared middleware, built-in request validation/binding, and automatic OPTIONS/CORS handling.
    * When to use a framework: Chi is my default choice when I need route groups, middleware chaining, and URL parameters on Go versions before 1.22. Chi is just a router that wraps the standard `http.Handler` interface, so your handlers are portable. Gin is appropriate when you want performance-optimized routing (radix tree), built-in JSON binding/validation, and your team is comfortable with Gin's non-standard `*gin.Context` instead of `http.ResponseWriter + *http.Request`. Echo is similar to Gin in philosophy.
    * My recommendation for new projects: start with the standard library plus Go 1.22 routing. Add Chi if you need route groups or are on an older Go version. Only reach for Gin/Echo if your team already knows them or you need their specific middleware ecosystem.

    **Follow-up: Explain the middleware pattern in Go. How does `func(http.Handler) http.Handler` enable composable middleware?**

    The type `func(http.Handler) http.Handler` is a function that takes a handler and returns a new handler that wraps the original. This is the decorator pattern applied to HTTP handling. Each middleware adds behavior (logging, auth, CORS, rate limiting) before or after calling the inner handler. Because every middleware has the same signature, they compose naturally. A `Chain` helper reverses this nesting for readability. The beauty is that any middleware works with any handler from any library, because they all speak the same `http.Handler` interface. This is why Go does not need a monolithic framework -- the interface IS the framework.
  </Accordion>

  <Accordion title="You are building a JSON API in Go. Walk me through the security considerations for handling request bodies, and show me the production-grade pattern.">
    **Strong Answer:**

    * First, limit the request body size. An attacker can send a multi-gigabyte body to exhaust memory. Use `http.MaxBytesReader(w, r.Body, 1<<20)` to cap at 1MB (or whatever is appropriate). This replaces `r.Body` with a reader that returns an error when the limit is exceeded.
    * Second, use `json.NewDecoder(r.Body).Decode(&req)` instead of `io.ReadAll` + `json.Unmarshal`. The decoder streams the JSON instead of loading the entire body into memory, and with `MaxBytesReader`, it fails early on oversized payloads.
    * Third, call `decoder.DisallowUnknownFields()` to reject JSON with fields not in your struct. This catches typos and prevents unexpected data from silently being accepted.
    * Fourth, validate the decoded struct. Use a validation library like `go-playground/validator` with struct tags (`validate:"required,email"`), or write explicit validation logic. Never trust client input.
    * Fifth, prevent JSON injection: always set `Content-Type: application/json` on responses, never interpolate user input into JSON strings manually, and sanitize any data that might be rendered in HTML contexts.
    * Sixth, handle the error from `json.NewEncoder(w).Encode(response)`. If the client disconnects mid-response, this write can fail, and you want to log it rather than crash.

    **Follow-up: What are the critical timeout settings on http.Server and what happens if you do not set them?**

    Without timeouts, a slow client can hold a connection open indefinitely, exhausting your server's file descriptors and goroutines. `ReadTimeout` limits how long the server waits to read the full request. `WriteTimeout` limits how long the server allows for writing the response. `IdleTimeout` controls how long keep-alive connections stay open between requests. `ReadHeaderTimeout` (often overlooked) limits reading just the headers, protecting against slowloris attacks. In production, I set `ReadTimeout: 5s`, `WriteTimeout: 10s`, `IdleTimeout: 120s`, `ReadHeaderTimeout: 2s`. Without these, a single slow or malicious client can consume a goroutine and connection slot forever.
  </Accordion>

  <Accordion title="The default HTTP client in Go has no timeout. Explain why this is dangerous and show me the production-grade HTTP client configuration.">
    **Strong Answer:**

    * `http.DefaultClient` has `Timeout: 0`, which means requests can block indefinitely. If the target server is slow or unresponsive, your goroutine hangs forever. In a service handling concurrent requests, this quickly leads to goroutine exhaustion.
    * The production configuration includes: `Timeout: 30 * time.Second` on the client (overall request timeout), and detailed transport settings: `MaxIdleConns: 100`, `MaxIdleConnsPerHost: 10`, `IdleConnTimeout: 90 * time.Second`.
    * Additionally, always use `http.NewRequestWithContext(ctx, method, url, body)` to create requests with context-based cancellation. This ties the request to the caller's context, so if the caller times out or cancels, the HTTP request is aborted immediately.
    * Critical detail: always read and close the response body, even on error responses. If you do not drain the body, the underlying TCP connection cannot be reused. The pattern is: `defer resp.Body.Close()` and for error cases: `io.Copy(io.Discard, resp.Body)`.

    **Follow-up: How would you implement retry with exponential backoff for HTTP requests, and what requests should you NOT retry?**

    Retry only idempotent requests: GET, PUT, DELETE. Never retry POST unless the API supports idempotency keys. Retry on server errors (5xx) and network errors, but not on client errors (4xx). The backoff formula is `baseDelay * 2^attempt + jitter`, where jitter prevents thundering herd. Always check `ctx.Done()` between retries so cancellation is respected. And always close the response body of failed attempts before retrying, or you leak connections.
  </Accordion>
</AccordionGroup>
