Skip to content

cloudresty/rig

Repository files navigation

Rig

Home  /

 

Rig is a lightweight HTTP framework for Go that wraps net/http with zero external dependencies. Built for Go 1.22+, it provides the ergonomics of popular frameworks while staying true to the standard library.

 

Go Reference Go Tests Go Report Card GitHub Tag License

 

Features

  • Zero Dependencies - Only the Go standard library (core package)
  • Go 1.22+ Pattern Matching - Full support for method routing and path parameters
  • Middleware - Global, group, and per-route middleware with onion-style execution
  • Route Groups - Organize routes with shared prefixes and middleware
  • JSON Handling - Bind, BindStrict, and JSON response helpers
  • Static Files - Serve directories with a single line
  • Production Middleware - Built-in Recover, CORS, and Timeout middleware
  • Production-Safe Timeouts - Server and request timeouts with Slowloris protection
  • Graceful Shutdown - Zero-downtime deployments with RunGracefully()
  • Health Checks - Liveness and readiness probes with timeout support for Kubernetes
  • HTML Templates - Template rendering with layouts, partials, embed.FS, and content negotiation (render/ sub-package)
  • Authentication - API Key and Bearer Token middleware (auth/ sub-package)
  • Request ID - ULID-based request tracking (requestid/ sub-package)
  • Logging - Structured request logging with JSON support (logger/ sub-package)
  • Swagger UI - Optional sub-package for API documentation
  • Type-Safe Context - Generic GetType[T] for dependency injection
  • 99%+ Test Coverage - Battle-tested and production-ready

 

🔝 back to top

 

Installation

go get github.com/cloudresty/rig

Requires Go 1.22 or later.

 

🔝 back to top

 

Quick Start

package main

import (
    "net/http"

    "github.com/cloudresty/rig"
)

func main() {
    r := rig.New()

    // Add middleware
    r.Use(rig.Recover())
    r.Use(rig.DefaultCORS())

    // Simple route
    r.GET("/", func(c *rig.Context) error {
        return c.JSON(http.StatusOK, map[string]string{
            "message": "Hello, World!",
        })
    })

    // Path parameters (Go 1.22+)
    r.GET("/users/{id}", func(c *rig.Context) error {
        id := c.Param("id")
        return c.JSON(http.StatusOK, map[string]string{
            "user_id": id,
        })
    })

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

 

🔝 back to top

 

Route Groups

Organize routes with shared prefixes and middleware:

r := rig.New()

// API group with authentication middleware
api := r.Group("/api")
api.Use(authMiddleware)

api.GET("/users", listUsers)       // GET /api/users
api.POST("/users", createUser)     // POST /api/users
api.GET("/users/{id}", getUser)    // GET /api/users/{id}

// Nested groups
v1 := api.Group("/v1")
v1.GET("/status", getStatus)       // GET /api/v1/status

 

🔝 back to top

 

Middleware

Middleware follows the decorator pattern with onion-style execution:

// Custom middleware
func Logger() rig.MiddlewareFunc {
    return func(next rig.HandlerFunc) rig.HandlerFunc {
        return func(c *rig.Context) error {
            start := time.Now()
            err := next(c)
            log.Printf("%s %s %v", c.Method(), c.Path(), time.Since(start))
            return err
        }
    }
}

// Apply middleware
r.Use(rig.Recover())    // Global - catches panics
r.Use(rig.DefaultCORS()) // Global - enables CORS
r.Use(Logger())          // Global - logs requests

 

Built-in Middleware

Middleware Description
Recover() Catches panics and returns a 500 JSON error
DefaultCORS() Permissive CORS (allows all origins)
CORS(config) Configurable CORS with specific origins/methods/headers
Timeout(duration) Cancels request context after specified duration

 

Additional middleware sub-packages (see sections below):

Package Description
auth/ API Key and Bearer Token authentication
requestid/ ULID-based request ID generation
logger/ Structured request logging (text/JSON)

 

🔝 back to top

 

Request Handling

Path Parameters

r.GET("/users/{id}", func(c *rig.Context) error {
    id := c.Param("id")
    // ...
})

 

Query Parameters

r.GET("/search", func(c *rig.Context) error {
    q := c.Query("q")                    // Single value
    tags := c.QueryArray("tag")          // Multiple values: ?tag=a&tag=b
    page := c.QueryDefault("page", "1")  // With default
    // ...
})

 

JSON Body

r.POST("/users", func(c *rig.Context) error {
    var user User
    if err := c.Bind(&user); err != nil {
        return c.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()})
    }
    // Use c.BindStrict(&user) to reject unknown fields
    // ...
})

 

Form Data

r.POST("/login", func(c *rig.Context) error {
    username := c.FormValue("username")      // Body takes precedence over query
    password := c.PostFormValue("password")  // Body only
    // ...
})

 

🔝 back to top

 

Response Helpers

// JSON response
c.JSON(http.StatusOK, data)

// String response
c.WriteString("Hello")

// Redirect
c.Redirect(http.StatusFound, "/new-location")

// Serve a file
c.File("./reports/monthly.pdf")

// Raw bytes
c.Data(http.StatusOK, "image/png", pngBytes)

// Set status only
c.Status(http.StatusNoContent)

 

🔝 back to top

 

Static Files

Serve a directory of static files:

r.Static("/assets", "./public")
// GET /assets/css/style.css → serves ./public/css/style.css

 

Cache Control for Static Assets

For production, enable cache headers to improve performance:

// Cache for 1 year (recommended for versioned assets like app.v1.2.3.js)
r.Static("/assets", "./public", rig.StaticConfig{
    CacheControl: "public, max-age=31536000",
})

// Cache for 1 day (for assets that may change)
r.Static("/images", "./images", rig.StaticConfig{
    CacheControl: "public, max-age=86400",
})

// No caching (for development or dynamic assets)
r.Static("/uploads", "./uploads", rig.StaticConfig{
    CacheControl: "no-cache",
})

 

Cache-Control Value Use Case
public, max-age=31536000 Versioned assets (CSS, JS with hash in filename)
public, max-age=86400 Assets that may change daily
public, max-age=3600 Assets that may change hourly
no-cache Always revalidate with server
no-store Never cache (sensitive data)

 

🔝 back to top

 

Dependency Injection

Use the context store for request-scoped values:

// Middleware: inject dependencies
func InjectDB(db *Database) rig.MiddlewareFunc {
    return func(next rig.HandlerFunc) rig.HandlerFunc {
        return func(c *rig.Context) error {
            c.Set("db", db)
            return next(c)
        }
    }
}

// Handler: retrieve with type safety
r.GET("/users", func(c *rig.Context) error {
    db, err := rig.GetType[*Database](c, "db")
    if err != nil {
        return err
    }
    // Use db...
})

 

🔝 back to top

 

CORS Configuration

// Permissive (all origins)
r.Use(rig.DefaultCORS())

// Restrictive with exact origins
r.Use(rig.CORS(rig.CORSConfig{
    AllowOrigins: []string{"https://myapp.com", "https://admin.myapp.com"},
    AllowMethods: []string{"GET", "POST", "PUT", "DELETE"},
    AllowHeaders: []string{"Content-Type", "Authorization"},
}))

// Wildcard subdomain support
r.Use(rig.CORS(rig.CORSConfig{
    AllowOrigins: []string{
        "https://*.myapp.com",           // Matches any subdomain
        "https://*.staging.myapp.com",   // Matches nested subdomains
        "https://api.production.com",    // Exact match also works
    },
    AllowMethods: []string{"GET", "POST", "PUT", "DELETE"},
    AllowHeaders: []string{"Content-Type", "Authorization"},
}))

 

AllowOrigins patterns:

Pattern Matches Does Not Match
"*" All origins -
"https://example.com" Exact match only https://sub.example.com
"https://*.example.com" https://app.example.com, https://a.b.example.com https://example.com, http://app.example.com

 

🔝 back to top

 

Health Checks

Rig provides an opt-in health utility for liveness and readiness probes, perfect for Kubernetes deployments:

func main() {
    r := rig.New()
    db := connectDB()

    // Create the Health manager
    health := rig.NewHealth()

    // Add readiness check (don't send traffic if DB is down)
    health.AddReadinessCheck("database", func() error {
        return db.Ping()
    })

    // Add liveness check (is the app running?)
    health.AddLivenessCheck("ping", func() error {
        return nil // Always healthy
    })

    // Mount the handlers (user chooses the paths)
    h := r.Group("/health")
    h.GET("/live", health.LiveHandler())
    h.GET("/ready", health.ReadyHandler())

    r.Run(":8080")
}

 

Response format:

// GET /health/ready (all checks pass)
{ "status": "OK", "checks": { "database": "OK" } }

// GET /health/ready (a check fails)
{ "status": "Service Unavailable", "checks": { "database": "FAIL: connection refused" } }

 

Method Description
NewHealth() Creates a new Health manager with default 5s timeout
NewHealthWithConfig(config) Creates a Health manager with custom configuration
AddReadinessCheck(name, fn) Adds a check for traffic readiness (DB, Redis, etc.)
AddReadinessCheckContext(name, fn) Adds a context-aware check that respects timeouts
AddLivenessCheck(name, fn) Adds a check for app liveness (deadlock detection, etc.)
LiveHandler() Returns a handler for liveness probes
ReadyHandler() Returns a handler for readiness probes

 

Health Check Timeouts

Health checks have built-in timeout protection to prevent slow checks from blocking health endpoints:

// Custom timeout configuration
config := rig.HealthConfig{
    CheckTimeout: 10 * time.Second,  // Per-check timeout (default: 5s)
    Parallel:     true,              // Run checks concurrently (default: false)
}
health := rig.NewHealthWithConfig(config)

// Context-aware check (receives timeout via context)
health.AddReadinessCheckContext("database", func(ctx context.Context) error {
    return db.PingContext(ctx)  // Respects the timeout
})

// Per-check timeout override
health.AddReadinessCheckWithTimeout("slow-service", 30*time.Second, func(ctx context.Context) error {
    return slowService.HealthCheck(ctx)
})

 

🔝 back to top

 

Timeouts

Rig provides production-safe timeout defaults to protect against Slowloris attacks and cascading failures.

 

Server Timeouts (Infrastructure Layer)

The Run() method applies production-safe defaults automatically:

r := rig.New()
r.Run(":8080")  // Uses DefaultServerConfig()

 

Default Value Purpose
ReadHeaderTimeout 5s Critical Slowloris protection (headers only)
WriteTimeout 10s Prevents slow response consumption
IdleTimeout 120s Allows keep-alive but not indefinitely
ReadTimeout 0 No body limit (allows slow uploads)
MaxHeaderBytes 1MB Prevents header size attacks

 

Custom Configuration:

config := rig.DefaultServerConfig()
config.Addr = ":8080"
config.WriteTimeout = 30 * time.Second  // Allow longer responses

r.RunWithConfig(config)

 

🔝 back to top

 

Request Timeouts (Application Layer)

Use Timeout() middleware to cancel slow handlers and prevent cascading failures:

r := rig.New()
r.Use(rig.Timeout(5 * time.Second))

r.GET("/api/data", func(c *rig.Context) error {
    // This query will be cancelled if it takes > 5s
    result, err := db.QueryContext(c.Context(), "SELECT ...")
    if err != nil {
        return err  // Returns context.DeadlineExceeded if timed out
    }
    return c.JSON(http.StatusOK, result)
})

 

Custom timeout response:

r.Use(rig.TimeoutWithConfig(rig.TimeoutConfig{
    Timeout: 5 * time.Second,
    OnTimeout: func(c *rig.Context) error {
        return c.JSON(http.StatusGatewayTimeout, map[string]string{
            "error": "Request timed out",
        })
    },
}))

 

 
IMPORTANT
Handlers must check c.Context() for this to work effectively. Use db.QueryContext(c.Context(), ...) instead of db.Query(...).
 

 

🔝 back to top

 

Graceful Shutdown

Rig provides zero-downtime deployment support with RunGracefully(). When the server receives a shutdown signal (SIGINT or SIGTERM), it:

  1. Stops accepting new connections
  2. Waits for in-flight requests to complete
  3. Exits cleanly after the timeout

This prevents dropped connections and 502 errors during deployments.

func main() {
    r := rig.New()

    r.GET("/", func(c *rig.Context) error {
        time.Sleep(2 * time.Second) // Simulate work
        return c.JSON(http.StatusOK, map[string]string{"status": "done"})
    })

    // Ctrl+C will wait for the 2s request to finish before exiting
    if err := r.RunGracefully(":8080"); err != nil {
        log.Fatal(err)
    }
}

 

Method Description
Run(addr) Simple start for development/testing
RunGracefully(addr) Production-ready with graceful shutdown (recommended)
RunWithGracefulShutdown(config) Graceful shutdown with custom configuration
RunWithConfig(config) Custom config without graceful shutdown

 

Custom shutdown timeout:

config := rig.DefaultServerConfig()
config.Addr = ":8080"
config.ShutdownTimeout = 30 * time.Second  // Wait longer for in-flight requests

if err := r.RunWithGracefulShutdown(config); err != nil {
    log.Fatal(err)
}

 

🔝 back to top

 

Swagger UI

Rig provides optional Swagger UI support via a separate sub-package to keep the core framework dependency-free:

go get github.com/cloudresty/rig/swagger

 

Basic Usage with swaggo/swag

package main

import (
    "github.com/cloudresty/rig"
    "github.com/cloudresty/rig/swagger"
    _ "myapp/docs" // Generated by: swag init
)

func main() {
    r := rig.New()

    // Your API routes
    r.GET("/api/v1/users", handleUsers)

    // Register Swagger UI at /docs/
    sw := swagger.NewFromSwag("swagger")
    sw.Register(r, "/docs")

    r.Run(":8080")
}
// Access Swagger UI at http://localhost:8080/docs/

 

Usage with Custom Spec

spec := `{"openapi":"3.0.0","info":{"title":"My API","version":"1.0"}}`
sw := swagger.New(spec).
    WithTitle("My API Documentation").
    WithDocExpansion("list")
sw.Register(r, "/api-docs")

 

Usage with Route Groups

api := r.Group("/api/v1")
sw := swagger.NewFromSwag("swagger")
sw.RegisterGroup(api, "/docs")
// Access at /api/v1/docs/

 

Method Description
New(specJSON) Creates Swagger UI with a JSON spec string
NewFromSwag(name) Creates Swagger UI from swaggo/swag registered spec
WithTitle(title) Sets the page title
WithDeepLinking(bool) Enables/disables URL deep linking (default: true)
WithDocExpansion(mode) Sets expansion mode: "list", "full", "none"
Register(router, path) Registers routes on a Router
RegisterGroup(group, path) Registers routes on a RouteGroup

 

🔝 back to top

 

Authentication Middleware

The auth/ sub-package provides API Key and Bearer Token authentication:

go get github.com/cloudresty/rig/auth

 

API Key Authentication

import "github.com/cloudresty/rig/auth"

// Simple: validate against a list of keys
api := r.Group("/api")
api.Use(auth.APIKeySimple("key1", "key2", "key3"))

// Advanced: custom validation with identity
api.Use(auth.APIKey(auth.APIKeyConfig{
    Name:   "X-API-Key",        // Header name (default)
    Source: "header",           // "header" or "query"
    Validator: func(key string) (identity string, valid bool) {
        if key == os.Getenv("API_KEY") {
            return "my-service", true
        }
        return "", false
    },
}))

// In handlers, get the authenticated identity
r.GET("/profile", func(c *rig.Context) error {
    identity := auth.GetIdentity(c)  // Returns identity from Validator
    method := auth.GetMethod(c)      // Returns "api_key" or "bearer"
    return c.JSON(http.StatusOK, map[string]string{"user": identity})
})

 

Bearer Token Authentication

api.Use(auth.Bearer(auth.BearerConfig{
    Realm: "API",
    Validator: func(token string) (identity string, valid bool) {
        // Validate JWT or lookup token
        claims, err := validateJWT(token)
        if err != nil {
            return "", false
        }
        return claims.UserID, true
    },
}))

 

Function Description
APIKeySimple(keys...) Simple API key validation (constant-time comparison)
APIKey(config) Configurable API key middleware
Bearer(config) Bearer token middleware (RFC 6750)
GetIdentity(c) Get authenticated identity from context
GetMethod(c) Get auth method ("api_key" or "bearer")
IsAuthenticated(c) Check if request is authenticated

 

🔝 back to top

 

Request ID Middleware

The requestid/ sub-package generates unique request IDs using ULIDs:

go get github.com/cloudresty/rig/requestid

 

import "github.com/cloudresty/rig/requestid"

r := rig.New()

// Add request ID middleware (generates ULID for each request)
r.Use(requestid.New())

// With custom configuration
r.Use(requestid.New(requestid.Config{
    Header:     "X-Request-ID",  // Response header (default)
    TrustProxy: true,            // Trust incoming X-Request-ID header
    Generator: func() (string, error) {
        return uuid.New().String(), nil  // Use UUID instead of ULID
    },
}))

// In handlers, get the request ID
r.GET("/", func(c *rig.Context) error {
    reqID := requestid.Get(c)
    return c.JSON(http.StatusOK, map[string]string{
        "request_id": reqID,
    })
})

 

Function Description
New() Create middleware with default config
New(config) Create middleware with custom config
Get(c) Get request ID from context

 

🔝 back to top

 

Logger Middleware

The logger/ sub-package provides structured request logging:

go get github.com/cloudresty/rig/logger

 

import "github.com/cloudresty/rig/logger"

r := rig.New()

// Add request ID first (logger will include it)
r.Use(requestid.New())

// Add logger middleware
r.Use(logger.New(logger.Config{
    Format:    logger.FormatJSON,              // FormatText or FormatJSON
    Output:    os.Stdout,                       // io.Writer
    SkipPaths: []string{"/health", "/ready"},  // Don't log these paths
}))

 

Text format output:

2024/01/15 10:30:45 | 200 |    1.234ms | 192.168.1.1 | GET /api/users | req_id: 01HQ...

JSON format output:

{"time":"2024-01-15T10:30:45Z","status":200,"latency":"1.234ms","latency_ms":1.234,"client_ip":"192.168.1.1","method":"GET","path":"/api/users","request_id":"01HQ..."}

 

Option Description
Format FormatText (default) or FormatJSON
Output io.Writer for log output (default: os.Stdout)
SkipPaths Paths to exclude from logging (e.g., health checks)

 

🔝 back to top

 

HTML Template Rendering

The render sub-package provides HTML template rendering with layouts, partials, hot reloading, and content negotiation.

go get github.com/cloudresty/rig/render

 

Basic Usage

import (
    "github.com/cloudresty/rig"
    "github.com/cloudresty/rig/render"
)

func main() {
    engine := render.New(render.Config{
        Directory: "./templates",
    })

    r := rig.New()
    r.Use(engine.Middleware())

    r.GET("/", func(c *rig.Context) error {
        return render.HTML(c, http.StatusOK, "home", map[string]any{
            "Title": "Welcome",
            "User":  "John",
        })
    })

    r.Run(":8080")
}

 

🔝 back to top

 

Embedded Templates (embed.FS)

For single-binary deployments, embed templates directly into your Go binary:

import (
    "embed"
    "github.com/cloudresty/rig/render"
)

//go:embed templates/*
var templateFS embed.FS

func main() {
    engine := render.New(render.Config{
        FileSystem: templateFS,
        Directory:  "templates",
    })
    // Templates are now compiled into the binary!
}

 

🔝 back to top

 

With Layouts

engine := render.New(render.Config{
    Directory: "./templates",
    Layout:    "layouts/base", // Base layout template
})

 

Layout template (templates/layouts/base.html):

<!DOCTYPE html>
<html>
<head><title>{{.Data.Title}}</title></head>
<body>
    <header>My Site</header>
    <main>{{.Content}}</main>
    <footer>© 2026</footer>
</body>
</html>

 

Page template (templates/home.html):

<h1>Welcome, {{.User}}!</h1>

 

Layout Data Access:

In layouts, data is available via {{.Data}} (works with both structs and maps):

Expression Description
{{.Content}} Rendered page content
{{.Data.Title}} Access data fields (works with structs!)
{{.Title}} Direct access (backward compatible with maps)

 

🔝 back to top

 

Partials

Templates starting with underscore (_) are automatically treated as partials and available to all templates:

templates/
├── _sidebar.html    ← Partial (available everywhere)
├── _footer.html     ← Partial (available everywhere)
├── home.html
└── about.html

 

Use partials in any template:

<div class="page">
    {{template "_sidebar" .}}
    <main>{{.Content}}</main>
    {{template "_footer" .}}
</div>

 

🔝 back to top

 

Shared Directories (Component-Based Architecture)

For larger applications, use SharedDirs to designate entire directories as globally available partials. This enables component-based architecture without requiring underscore prefixes:

engine := render.New(render.Config{
    Directory:  "./templates",
    Layout:     "layouts/base",
    SharedDirs: []string{"components", "layouts", "base"},
})

 

Example directory structure:

templates/
├── components/           ← Shared (available everywhere)
│   ├── button.html
│   ├── card.html
│   └── forms/
│       └── input.html
├── base/                 ← Shared (available everywhere)
│   └── modal.html
├── layouts/              ← Shared (available everywhere)
│   └── base.html
└── features/             ← Feature templates (isolated)
    ├── dashboard/
    │   └── index.html
    └── users/
        ├── list.html
        └── profile.html

 

Feature templates can include shared components:

<!-- features/dashboard/index.html -->
<h1>Dashboard</h1>
{{template "components/button" "Click Me"}}
{{template "components/card" .Stats}}
{{template "base/modal" .ModalData}}

 

This approach keeps feature pages isolated while sharing common components across the application. The underscore convention still works alongside SharedDirs - files starting with _ are always treated as partials regardless of their directory.

 

🔝 back to top

 

Rendering Partials Directly (HTMX / AJAX)

For HTMX requests, AJAX updates, or any scenario where you need to render a fragment without the layout wrapper, use Partial():

// HTMX endpoint - returns just the table rows, no layout
r.GET("/users/table", func(c *rig.Context) error {
    users := getUsers()
    return render.Partial(c, http.StatusOK, "components/user-table", map[string]any{
        "Users": users,
    })
})

// Dynamic widget update
r.GET("/dashboard/stats", func(c *rig.Context) error {
    stats := getStats()
    return render.Partial(c, http.StatusOK, "_stats-widget", stats)
})

 

Function Description
render.Partial(c, status, name, data) Render partial via middleware (recommended)
render.PartialDirect(c, engine, status, name, data) Render partial with explicit engine
engine.RenderPartial(name, data) Low-level: returns HTML string

 

Key differences from HTML():

  • Looks up templates in the shared partials set only (not regular templates)
  • Never wraps output in a layout
  • Perfect for HTMX hx-get / hx-post responses

 

🔝 back to top

 

JSON and XML Responses

Render JSON or XML directly without templates:

// JSON response
r.GET("/api/users", func(c *rig.Context) error {
    return render.JSON(c, http.StatusOK, users)
})

// XML response
r.GET("/api/users.xml", func(c *rig.Context) error {
    return render.XML(c, http.StatusOK, users)
})

 

🔝 back to top

 

Content Negotiation

Use Auto() to automatically select the response format based on the Accept header:

r.GET("/users", func(c *rig.Context) error {
    // Returns HTML for browsers, JSON for API clients
    return render.Auto(c, http.StatusOK, "users/list", users)
})

 

Accept Header Response Format
application/json JSON
application/xml or text/xml XML
text/html or other HTML (if template provided)
No template provided JSON (fallback)

 

🔝 back to top

 

Error Pages (Production)

Use HTMLSafe() to automatically render a pretty error page when template rendering fails:

r.GET("/page", func(c *rig.Context) error {
    return render.HTMLSafe(c, http.StatusOK, "page", data, "errors/500")
})

 

If "page" fails to render, it automatically falls back to "errors/500" with error details:

<!-- templates/errors/500.html -->
<h1>Something went wrong</h1>
<p>Error: {{.Error}}</p>
<p>Status: {{.StatusCode}}</p>

 

🔝 back to top

 

Development Mode

Enable hot reloading during development:

engine := render.New(render.Config{
    Directory: "./templates",
    DevMode:   true, // Reloads templates on each request
})

 

🔝 back to top

 

HTML Minification

Enable HTML minification for production to reduce bandwidth and improve page load times:

engine := render.New(render.Config{
    Directory: "./templates",
    Minify:    true, // Minify HTML output
})

 

The minifier is enterprise-grade and safe:

Feature Description
Block element optimization Removes whitespace between block elements (<div>, <p>, <section>, etc.)
Inline element preservation Preserves spaces between inline elements (<span>A</span> <span>B</span> stays intact)
Protected blocks Preserves whitespace in <pre>, <script>, <style>, and <textarea>
Comment removal Removes HTML comments while preserving IE conditional comments
JavaScript safety Preserves // comments and formatting inside <script> tags

 

🔝 back to top

 

Custom Template Functions

engine := render.New(render.Config{
    Directory: "./templates",
    Funcs: template.FuncMap{
        "upper": strings.ToUpper,
        "formatDate": func(t time.Time) string {
            return t.Format("Jan 2, 2006")
        },
    },
})
// Or use chained methods:
engine.AddFunc("lower", strings.ToLower)

 

Use in templates: {{upper .Name}} or {{formatDate .CreatedAt}}

 

🔝 back to top

 

Built-in Functions

Function Description Usage
safe Render trusted HTML without escaping {{safe .RawHTML}}
safeAttr Render trusted HTML attribute {{safeAttr .Attr}}
safeURL Render trusted URL {{safeURL .Link}}
dump Debug helper - outputs data as formatted JSON {{dump .}}

 

The dump function is invaluable during development:

<!-- Debug: see what data was passed to the template -->
{{dump .Data}}

Outputs:

<pre>{
  "Title": "My Page",
  "User": {
    "Name": "John"
  }
}</pre>

 

🔝 back to top

 

Using Sprig Functions

For 100+ additional template functions (string manipulation, math, dates, etc.), integrate Sprig:

import "github.com/Masterminds/sprig/v3"

engine := render.New(render.Config{
    Directory: "./templates",
})
engine.AddFuncs(sprig.FuncMap())

 

Now you can use functions like {{.Name | upper}}, {{now | date "2006-01-02"}}, and many more.

 

🔝 back to top

 

Custom Delimiters (Vue.js / Angular / Alpine.js)

When using frontend frameworks that also use {{ }} syntax, configure custom delimiters:

engine := render.New(render.Config{
    Directory: "./templates",
    Delims:    []string{"[[", "]]"}, // Use [[ ]] for Go templates
})

 

Now your templates can mix Go and Vue/Angular syntax:

<div>
    <!-- Go template (rendered on server) -->
    <h1>[[ .Title ]]</h1>

    <!-- Vue.js binding (rendered on client) -->
    <p>{{ message }}</p>
</div>

 

🔝 back to top

 

Debugging

List loaded templates and partials:

engine.TemplateNames() // Returns all template names
engine.PartialNames()  // Returns all partial names (files starting with _)

 

🔝 back to top

 

Examples

The examples/ directory contains runnable examples:

Example Description
basic-api REST API with middleware, dependency injection, and route groups
health-checks Kubernetes-style liveness and readiness probes
swagger-ui API with integrated Swagger UI documentation
auth-middleware API Key authentication using the auth/ package
logging Request logging with request ID tracking
render-templates HTML templates with layouts, partials, and content negotiation

 

🔝 back to top

 

API Reference

Context Methods

Method Description
Param(name) Get path parameter
Query(key) Get query parameter
QueryDefault(key, def) Get query parameter with default
QueryArray(key) Get all values for a query parameter
FormValue(key) Get form value (body precedence)
PostFormValue(key) Get form value (body only)
GetHeader(key) Get request header
SetHeader(key, value) Set response header
Bind(v) Decode JSON body
BindStrict(v) Decode JSON body (reject unknown fields)
JSON(code, v) Send JSON response
Status(code) Set status code
Redirect(code, url) Send redirect
File(path) Serve a file
Data(code, contentType, data) Send raw bytes
Set(key, value) Store request-scoped value
Get(key) Retrieve stored value
MustGet(key) Retrieve stored value (panics if missing)
Context() Get context.Context
SetContext(ctx) Set context.Context
Request() Get *http.Request
Writer() Get http.ResponseWriter

 

Router Methods

Method Description
New() Create a new router
Use(middleware...) Add global middleware
Handle(pattern, handler) Register a handler
GET/POST/PUT/DELETE/PATCH/OPTIONS/HEAD(path, handler) Register method-specific handler
Group(prefix) Create a route group
Static(path, root) Serve static files
ServeHTTP(w, r) Implement http.Handler

 

🔝 back to top

 

License

This project is licensed under the MIT License - see the LICENSE file for details.

 

🔝 back to top

 

 


Cloudresty

Website  |  LinkedIn  |  BlueSky  |  GitHub  |  Docker Hub

© Cloudresty - All rights reserved

 

About

Rig

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages