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>© 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)
}
}Best Practice: Always set appropriate Content-Type headers, use proper HTTP status codes, and implement caching strategies for better performance and user experience.