Response Management Guide

Learn how to send different types of responses, set headers, handle content negotiation, and manage status codes in Squirrel.

JSONTemplatesStatus Codes
JSON Responses
Send structured JSON data with proper headers and status codes
package main

import (
    "encoding/json"
    "net/http"
    "time"
    "github.com/squirrel-land/squirrel"
)

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

type APIResponse struct {
    Success bool        `json:"success"`
    Data    interface{} `json:"data,omitempty"`
    Error   string      `json:"error,omitempty"`
    Meta    *Meta       `json:"meta,omitempty"`
}

type Meta struct {
    Page       int `json:"page"`
    PerPage    int `json:"per_page"`
    Total      int `json:"total"`
    TotalPages int `json:"total_pages"`
}

// Helper function to send JSON responses
func sendJSON(w http.ResponseWriter, data interface{}, statusCode int) {
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(statusCode)
    
    if err := json.NewEncoder(w).Encode(data); err != nil {
        http.Error(w, "Error encoding JSON", http.StatusInternalServerError)
    }
}

// Success response helper
func sendSuccess(w http.ResponseWriter, data interface{}) {
    response := APIResponse{
        Success: true,
        Data:    data,
    }
    sendJSON(w, response, http.StatusOK)
}

// Error response helper
func sendError(w http.ResponseWriter, message string, statusCode int) {
    response := APIResponse{
        Success: false,
        Error:   message,
    }
    sendJSON(w, response, statusCode)
}

// Paginated response helper
func sendPaginated(w http.ResponseWriter, data interface{}, page, perPage, total int) {
    totalPages := (total + perPage - 1) / perPage
    
    response := APIResponse{
        Success: true,
        Data:    data,
        Meta: &Meta{
            Page:       page,
            PerPage:    perPage,
            Total:      total,
            TotalPages: totalPages,
        },
    }
    sendJSON(w, response, http.StatusOK)
}

func getUsersHandler(w http.ResponseWriter, r *http.Request) {
    users := []User{
        {ID: 1, Name: "Alice", Email: "alice@example.com", CreatedAt: time.Now()},
        {ID: 2, Name: "Bob", Email: "bob@example.com", CreatedAt: time.Now()},
    }
    
    sendPaginated(w, users, 1, 10, len(users))
}

func getUserHandler(w http.ResponseWriter, r *http.Request) {
    id := squirrel.GetParam(r, "id")
    
    // Simulate user lookup
    if id == "999" {
        sendError(w, "User not found", http.StatusNotFound)
        return
    }
    
    user := User{
        ID:        1,
        Name:      "Alice",
        Email:     "alice@example.com",
        CreatedAt: time.Now(),
    }
    
    sendSuccess(w, user)
}

func createUserHandler(w http.ResponseWriter, r *http.Request) {
    var user User
    if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
        sendError(w, "Invalid JSON", http.StatusBadRequest)
        return
    }
    
    // Simulate user creation
    user.ID = 123
    user.CreatedAt = time.Now()
    
    // Return 201 Created for successful creation
    response := APIResponse{
        Success: true,
        Data:    user,
    }
    sendJSON(w, response, http.StatusCreated)
}

func main() {
    mux := squirrel.NewSqurlMux()
    
    mux.Get("/users", getUsersHandler)
    mux.Get("/users/{id}", getUserHandler)
    mux.Post("/users", createUserHandler)
    
    http.ListenAndServe(":8080", mux)
}
HTML Templates
Render dynamic HTML content using Go templates
package main

import (
    "html/template"
    "net/http"
    "time"
    "github.com/squirrel-land/squirrel"
)

type PageData struct {
    Title   string
    Message string
    Users   []User
    Now     time.Time
}

type User struct {
    ID    int
    Name  string
    Email string
    Admin bool
}

// Template cache
var templates = template.Must(template.ParseGlob("templates/*.html"))

func renderTemplate(w http.ResponseWriter, tmpl string, data interface{}) {
    w.Header().Set("Content-Type", "text/html; charset=utf-8")
    
    if err := templates.ExecuteTemplate(w, tmpl, data); err != nil {
        http.Error(w, "Error rendering template", http.StatusInternalServerError)
        return
    }
}

func homeHandler(w http.ResponseWriter, r *http.Request) {
    data := PageData{
        Title:   "Welcome to Squirrel",
        Message: "Hello from Squirrel web framework!",
        Now:     time.Now(),
    }
    
    renderTemplate(w, "home.html", data)
}

func usersHandler(w http.ResponseWriter, r *http.Request) {
    users := []User{
        {ID: 1, Name: "Alice", Email: "alice@example.com", Admin: true},
        {ID: 2, Name: "Bob", Email: "bob@example.com", Admin: false},
        {ID: 3, Name: "Charlie", Email: "charlie@example.com", Admin: false},
    }
    
    data := PageData{
        Title: "Users List",
        Users: users,
        Now:   time.Now(),
    }
    
    renderTemplate(w, "users.html", data)
}

func main() {
    mux := squirrel.NewSqurlMux()
    
    mux.Get("/", homeHandler)
    mux.Get("/users", usersHandler)
    
    // Serve static files
    mux.HandleFunc("/static/", http.StripPrefix("/static/", 
        http.FileServer(http.Dir("static"))).ServeHTTP)
    
    http.ListenAndServe(":8080", mux)
}

/* templates/base.html */
`<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{{.Title}}</title>
    <link rel="stylesheet" href="/static/css/style.css">
</head>
<body>
    <nav>
        <a href="/">Home</a>
        <a href="/users">Users</a>
    </nav>
    <main>
        {{template "content" .}}
    </main>
    <footer>
        <p>&copy; 2024 Squirrel App. Generated at {{.Now.Format "2006-01-02 15:04:05"}}</p>
    </footer>
</body>
</html>`

/* templates/home.html */
`{{template "base.html" .}}
{{define "content"}}
    <h1>{{.Title}}</h1>
    <p>{{.Message}}</p>
    <p>Current time: {{.Now.Format "January 2, 2006 at 3:04 PM"}}</p>
{{end}}`

/* templates/users.html */
`{{template "base.html" .}}
{{define "content"}}
    <h1>{{.Title}}</h1>
    {{if .Users}}
        <table>
            <thead>
                <tr>
                    <th>ID</th>
                    <th>Name</th>
                    <th>Email</th>
                    <th>Role</th>
                </tr>
            </thead>
            <tbody>
                {{range .Users}}
                <tr>
                    <td>{{.ID}}</td>
                    <td>{{.Name}}</td>
                    <td>{{.Email}}</td>
                    <td>{{if .Admin}}Admin{{else}}User{{end}}</td>
                </tr>
                {{end}}
            </tbody>
        </table>
    {{else}}
        <p>No users found.</p>
    {{end}}
{{end}}`
Status Codes & Headers
Properly set HTTP status codes and response headers
package main

import (
    "encoding/json"
    "fmt"
    "net/http"
    "time"
    "github.com/squirrel-land/squirrel"
)

// Response helpers with proper status codes
func sendCreated(w http.ResponseWriter, data interface{}) {
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusCreated) // 201
    json.NewEncoder(w).Encode(data)
}

func sendNoContent(w http.ResponseWriter) {
    w.WriteHeader(http.StatusNoContent) // 204
}

func sendNotFound(w http.ResponseWriter, message string) {
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusNotFound) // 404
    json.NewEncoder(w).Encode(map[string]string{"error": message})
}

func sendConflict(w http.ResponseWriter, message string) {
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusConflict) // 409
    json.NewEncoder(w).Encode(map[string]string{"error": message})
}

func sendUnprocessableEntity(w http.ResponseWriter, errors map[string]string) {
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusUnprocessableEntity) // 422
    json.NewEncoder(w).Encode(map[string]interface{}{
        "error":  "Validation failed",
        "errors": errors,
    })
}

// Custom headers for different scenarios
func apiHandler(w http.ResponseWriter, r *http.Request) {
    // CORS headers
    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")
    
    // Security headers
    w.Header().Set("X-Content-Type-Options", "nosniff")
    w.Header().Set("X-Frame-Options", "DENY")
    w.Header().Set("X-XSS-Protection", "1; mode=block")
    
    // API versioning
    w.Header().Set("API-Version", "v1.0")
    
    // Rate limiting info
    w.Header().Set("X-RateLimit-Limit", "100")
    w.Header().Set("X-RateLimit-Remaining", "95")
    w.Header().Set("X-RateLimit-Reset", fmt.Sprintf("%d", time.Now().Add(time.Hour).Unix()))
    
    data := map[string]string{"message": "API response with custom headers"}
    json.NewEncoder(w).Encode(data)
}

// File download with proper headers
func downloadHandler(w http.ResponseWriter, r *http.Request) {
    filename := squirrel.GetParam(r, "filename")
    
    // Set headers for file download
    w.Header().Set("Content-Type", "application/octet-stream")
    w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename="%s"", filename))
    w.Header().Set("Content-Transfer-Encoding", "binary")
    
    // Simulate file content
    content := []byte("This is the file content for " + filename)
    w.Header().Set("Content-Length", fmt.Sprintf("%d", len(content)))
    
    w.Write(content)
}

// Caching headers
func cachedResourceHandler(w http.ResponseWriter, r *http.Request) {
    // Check if client has cached version
    ifModifiedSince := r.Header.Get("If-Modified-Since")
    lastModified := time.Now().AddDate(0, 0, -1) // Yesterday
    
    if ifModifiedSince != "" {
        if t, err := time.Parse(http.TimeFormat, ifModifiedSince); err == nil {
            if !lastModified.After(t) {
                w.WriteHeader(http.StatusNotModified) // 304
                return
            }
        }
    }
    
    // Set caching headers
    w.Header().Set("Last-Modified", lastModified.Format(http.TimeFormat))
    w.Header().Set("Cache-Control", "public, max-age=3600") // 1 hour
    w.Header().Set("ETag", ""123456789"")
    
    // Send content
    w.Header().Set("Content-Type", "application/json")
    data := map[string]interface{}{
        "data":         "This is cached content",
        "last_updated": lastModified,
    }
    json.NewEncoder(w).Encode(data)
}

// Redirect responses
func redirectHandler(w http.ResponseWriter, r *http.Request) {
    redirectType := squirrel.GetParam(r, "type")
    
    switch redirectType {
    case "permanent":
        // 301 Permanent Redirect
        http.Redirect(w, r, "/new-location", http.StatusMovedPermanently)
    case "temporary":
        // 302 Temporary Redirect
        http.Redirect(w, r, "/temp-location", http.StatusFound)
    case "see-other":
        // 303 See Other (after POST)
        http.Redirect(w, r, "/success", http.StatusSeeOther)
    default:
        http.Error(w, "Invalid redirect type", http.StatusBadRequest)
    }
}

// Content negotiation
func contentNegotiationHandler(w http.ResponseWriter, r *http.Request) {
    accept := r.Header.Get("Accept")
    
    data := map[string]interface{}{
        "message": "Hello, World!",
        "time":    time.Now(),
    }
    
    switch {
    case strings.Contains(accept, "application/json"):
        w.Header().Set("Content-Type", "application/json")
        json.NewEncoder(w).Encode(data)
        
    case strings.Contains(accept, "application/xml"):
        w.Header().Set("Content-Type", "application/xml")
        xml := fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?>
<response>
    <message>%s</message>
    <time>%s</time>
</response>`, data["message"], data["time"])
        fmt.Fprint(w, xml)
        
    case strings.Contains(accept, "text/plain"):
        w.Header().Set("Content-Type", "text/plain")
        fmt.Fprintf(w, "Message: %s\nTime: %s", data["message"], data["time"])
        
    default:
        // Default to JSON
        w.Header().Set("Content-Type", "application/json")
        json.NewEncoder(w).Encode(data)
    }
}

func main() {
    mux := squirrel.NewSqurlMux()
    
    mux.Get("/api/data", apiHandler)
    mux.Get("/download/{filename}", downloadHandler)
    mux.Get("/cached", cachedResourceHandler)
    mux.Get("/redirect/{type}", redirectHandler)
    mux.Get("/content", contentNegotiationHandler)
    
    http.ListenAndServe(":8080", mux)
}
Response Streaming
Stream large responses and implement Server-Sent Events
package main

import (
    "encoding/json"
    "fmt"
    "net/http"
    "time"
    "github.com/squirrel-land/squirrel"
)

// Stream large JSON array
func streamJSONHandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    
    // Start JSON array
    fmt.Fprint(w, "[")
    
    // Stream items one by one
    for i := 0; i < 1000; i++ {
        if i > 0 {
            fmt.Fprint(w, ",")
        }
        
        item := map[string]interface{}{
            "id":    i,
            "name":  fmt.Sprintf("Item %d", i),
            "value": i * 10,
            "time":  time.Now(),
        }
        
        json.NewEncoder(w).Encode(item)
        
        // Flush to send data immediately
        if flusher, ok := w.(http.Flusher); ok {
            flusher.Flush()
        }
        
        // Small delay to simulate processing
        time.Sleep(10 * time.Millisecond)
    }
    
    // Close JSON array
    fmt.Fprint(w, "]")
}

// Stream CSV data
func streamCSVHandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "text/csv")
    w.Header().Set("Content-Disposition", "attachment; filename="data.csv"")
    
    // Write CSV header
    fmt.Fprint(w, "ID,Name,Email,Created\n")
    
    // Stream CSV rows
    for i := 1; i <= 10000; i++ {
        row := fmt.Sprintf("%d,User%d,user%d@example.com,%s\n", 
            i, i, i, time.Now().Format("2006-01-02"))
        
        fmt.Fprint(w, row)
        
        if flusher, ok := w.(http.Flusher); ok {
            flusher.Flush()
        }
        
        // Batch processing - flush every 100 rows
        if i%100 == 0 {
            time.Sleep(1 * time.Millisecond)
        }
    }
}

// Progress tracking for long operations
func longOperationHandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "text/plain")
    
    steps := []string{
        "Initializing...",
        "Loading data...",
        "Processing records...",
        "Validating results...",
        "Generating report...",
        "Finalizing...",
        "Complete!",
    }
    
    for i, step := range steps {
        progress := float64(i+1) / float64(len(steps)) * 100
        message := fmt.Sprintf("[%.1f%%] %s\n", progress, step)
        
        fmt.Fprint(w, message)
        
        if flusher, ok := w.(http.Flusher); ok {
            flusher.Flush()
        }
        
        // Simulate work
        time.Sleep(500 * time.Millisecond)
    }
}