This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Router Package

Complete API reference for the rivaas.dev/router package.

This is the API reference for the rivaas.dev/router package. For learning-focused documentation, see the Router Guide.

Overview

The rivaas.dev/router package provides a high-performance HTTP router with comprehensive features:

  • Radix tree routing with bloom filters
  • Compiled route tables for performance
  • Built-in middleware support
  • Request binding and validation
  • OpenTelemetry native tracing
  • API versioning
  • Content negotiation

Package Structure

rivaas.dev/router/
├── router.go          # Core router and route registration
├── context.go         # Request context with pooling
├── route.go           # Route definitions and constraints
├── middleware/        # Built-in middleware
│   ├── accesslog/    # Structured access logging
│   ├── cors/         # CORS handling
│   ├── recovery/     # Panic recovery
│   └── ...           # More middleware
└── ...

Quick API Index

Core Types

Route Registration

  • HTTP Methods: GET(), POST(), PUT(), DELETE(), PATCH(), OPTIONS(), HEAD()
  • Route Groups: Group(prefix), Version(version)
  • Middleware: Use(middleware...)
  • Static Files: Static(), StaticFile(), StaticFS()

Request Handling

  • Parameters: Param(), Query(), PostForm()
  • Binding: Bind(), BindQuery(), BindJSON(), etc.
  • Validation: Validate(), BindAndValidate(), MustBindAndValidate()

Response Rendering

  • JSON: JSON(), PureJSON(), IndentedJSON(), SecureJSON()
  • Other: YAML(), String(), HTML(), Data()
  • Files: ServeFile(), Download(), DataFromReader()

Configuration

Performance

Routing Performance

  • Sub-microsecond routing: 119ns per operation
  • High throughput: 8.4M+ requests/second
  • Memory efficient: 16 bytes per request, 1 allocation
  • Context pooling: Automatic context reuse
  • Lock-free operations: Atomic operations for concurrent access

Optimization Features

  • Compiled routes: Pre-compiled routes for static and dynamic paths
  • Bloom filters: Fast negative lookups for static routes
  • First-segment index: ASCII-only route narrowing (O(1) lookup)
  • Parameter storage: Array-based for ≤8 params, map for >8
  • Type caching: Reflection information cached per struct type

Thread Safety

All router operations are concurrent-safe:

  • Route registration can occur from multiple goroutines
  • Route trees use atomic operations for concurrent access
  • Context pooling is thread-safe
  • Middleware execution is goroutine-safe

Next Steps

1 - API Reference

Core types and methods for the router package.

Router

router.New(opts ...Option) *Router

Creates a new router instance.

r := router.New()

// With options
r := router.New(
    router.WithTracing(),
    router.WithTracingServiceName("my-api"),
)

HTTP Method Handlers

Register routes for HTTP methods:

r.GET(path string, handlers ...HandlerFunc) *Route
r.POST(path string, handlers ...HandlerFunc) *Route
r.PUT(path string, handlers ...HandlerFunc) *Route
r.DELETE(path string, handlers ...HandlerFunc) *Route
r.PATCH(path string, handlers ...HandlerFunc) *Route
r.OPTIONS(path string, handlers ...HandlerFunc) *Route
r.HEAD(path string, handlers ...HandlerFunc) *Route

Example:

r.GET("/users", listUsersHandler)
r.POST("/users", createUserHandler)
r.GET("/users/:id", getUserHandler)

Middleware

r.Use(middleware ...HandlerFunc)

Adds global middleware to the router.

r.Use(Logger(), Recovery())

Route Groups

r.Group(prefix string, middleware ...HandlerFunc) *Group

Creates a new route group with the specified prefix and optional middleware.

api := r.Group("/api/v1")
api.Use(Auth())
api.GET("/users", listUsers)

API Versioning

r.Version(version string) *Group

Creates a version-specific route group.

v1 := r.Version("v1")
v1.GET("/users", listUsersV1)

Static Files

r.Static(relativePath, root string)
r.StaticFile(relativePath, filepath string)
r.StaticFS(relativePath string, fs http.FileSystem)

Example:

r.Static("/assets", "./public")
r.StaticFile("/favicon.ico", "./static/favicon.ico")

Route Introspection

r.Routes() []RouteInfo

Returns all registered routes for inspection.

Route

Constraints

Apply validation constraints to route parameters:

route.WhereInt(param string) *Route
route.WhereFloat(param string) *Route
route.WhereUUID(param string) *Route
route.WhereDate(param string) *Route
route.WhereDateTime(param string) *Route
route.WhereEnum(param string, values ...string) *Route
route.WhereRegex(param, pattern string) *Route

Example:

r.GET("/users/:id", getUserHandler).WhereInt("id")
r.GET("/entities/:uuid", getEntityHandler).WhereUUID("uuid")
r.GET("/status/:state", getStatusHandler).WhereEnum("state", "active", "pending")

Group

Route groups support the same methods as Router, with the group’s prefix automatically prepended.

group.GET(path string, handlers ...HandlerFunc) *Route
group.POST(path string, handlers ...HandlerFunc) *Route
group.Use(middleware ...HandlerFunc)
group.Group(prefix string, middleware ...HandlerFunc) *Group

HandlerFunc

type HandlerFunc func(*Context)

Handler function signature for route handlers and middleware.

Example:

func handler(c *router.Context) {
    c.JSON(200, map[string]string{"message": "Hello"})
}

Next Steps

2 - Router Options

Configuration options for Router initialization.

Router options are passed to router.New() or router.MustNew() to configure the router.

Router Creation

// With error handling
r, err := router.New(opts...)
if err != nil {
    log.Fatalf("Failed to create router: %v", err)
}

// Panics on invalid configuration. Use at startup.
r := router.MustNew(opts...)

Versioning Options

WithVersioning(opts ...version.Option)

Configures API versioning support using functional options from the version package.

import "rivaas.dev/router/version"

r := router.MustNew(
    router.WithVersioning(
        version.WithHeaderDetection("X-API-Version"),
        version.WithDefault("v1"),
    ),
)

With multiple detection strategies:

r := router.MustNew(
    router.WithVersioning(
        version.WithPathDetection("/api/v{version}"),
        version.WithHeaderDetection("X-API-Version"),
        version.WithQueryDetection("v"),
        version.WithDefault("v2"),
        version.WithResponseHeaders(),
        version.WithSunsetEnforcement(),
    ),
)

Diagnostic Options

WithDiagnostics(handler DiagnosticHandler)

Sets a diagnostic handler for informational events like header injection attempts or configuration warnings.

import "log/slog"

handler := router.DiagnosticHandlerFunc(func(e router.DiagnosticEvent) {
    slog.Warn(e.Message, "kind", e.Kind, "fields", e.Fields)
})

r := router.MustNew(router.WithDiagnostics(handler))

With metrics:

handler := router.DiagnosticHandlerFunc(func(e router.DiagnosticEvent) {
    metrics.Increment("router.diagnostics", "kind", string(e.Kind))
})

Server Options

WithH2C(enable bool)

Enables HTTP/2 Cleartext (h2c) support.

r := router.MustNew(router.WithH2C(true))

WithServerTimeouts(readHeader, read, write, idle time.Duration)

Configures HTTP server timeouts to prevent slowloris attacks and resource exhaustion.

Defaults (if not set):

  • ReadHeaderTimeout: 5s
  • ReadTimeout: 15s
  • WriteTimeout: 30s
  • IdleTimeout: 60s
r := router.MustNew(router.WithServerTimeouts(
    10*time.Second,  // ReadHeaderTimeout
    30*time.Second,  // ReadTimeout
    60*time.Second,  // WriteTimeout
    120*time.Second, // IdleTimeout
))

Performance Options

WithRouteCompilation(enabled bool) / WithoutRouteCompilation()

Controls compiled route matching. When enabled (default), routes are pre-compiled for faster lookup.

// Enabled by default
r := router.MustNew(router.WithRouteCompilation(true))

// Disable for debugging
r := router.MustNew(router.WithoutRouteCompilation())

WithBloomFilterSize(size uint64)

Sets the bloom filter size for compiled routes. Larger sizes reduce false positives.

Default: 1000
Recommended: 2-3x the number of static routes

r := router.MustNew(router.WithBloomFilterSize(2000)) // For ~1000 routes

WithBloomFilterHashFunctions(numFuncs int)

Sets the number of hash functions for bloom filters.

Default: 3
Range: 1-10 (clamped)

r := router.MustNew(router.WithBloomFilterHashFunctions(4))

WithCancellationCheck(enabled bool) / WithoutCancellationCheck()

Controls context cancellation checking in the middleware chain. When enabled (default), the router checks for canceled contexts between handlers.

// Enabled by default
r := router.MustNew(router.WithCancellationCheck(true))

// Disable if you handle cancellation manually
r := router.MustNew(router.WithoutCancellationCheck())

Complete Example

package main

import (
    "log/slog"
    "net/http"
    "os"
    "time"
    
    "rivaas.dev/router"
    "rivaas.dev/router/version"
)

func main() {
    logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
    
    // Diagnostic handler
    diagHandler := router.DiagnosticHandlerFunc(func(e router.DiagnosticEvent) {
        logger.Warn(e.Message, "kind", e.Kind, "fields", e.Fields)
    })
    
    // Create router with options
    r := router.MustNew(
        // Versioning
        router.WithVersioning(
            version.WithHeaderDetection("API-Version"),
            version.WithDefault("v1"),
        ),
        
        // Server configuration
        router.WithServerTimeouts(
            10*time.Second,
            30*time.Second,
            60*time.Second,
            120*time.Second,
        ),
        
        // Performance tuning
        router.WithBloomFilterSize(2000),
        
        // Diagnostics
        router.WithDiagnostics(diagHandler),
    )
    
    r.GET("/", func(c *router.Context) {
        c.JSON(200, map[string]string{"message": "Hello"})
    })
    
    http.ListenAndServe(":8080", r)
}

Observability Options

import (
    "rivaas.dev/app"
    "rivaas.dev/tracing"
    "rivaas.dev/metrics"
)

application := app.New(
    app.WithServiceName("my-api"),
    app.WithObservability(
        app.WithTracing(tracing.WithSampleRate(0.1)),
        app.WithMetrics(metrics.WithPrometheus()),
        app.WithExcludePaths("/health", "/metrics"),
    ),
)

Next Steps

3 - Context API

Complete reference for Context methods.

The Context provides access to request/response and utility methods.

Request Information

URL Parameters

c.Param(key string) string

Returns URL parameter value from the route path.

// Route: /users/:id
userID := c.Param("id")
c.AllParams() map[string]string

Returns all URL path parameters as a map.

Query Parameters

c.Query(key string) string
c.QueryDefault(key, defaultValue string) string
c.AllQueries() map[string]string
// GET /search?q=golang&page=2
query := c.Query("q")           // "golang"
page := c.QueryDefault("page", "1") // "2"
all := c.AllQueries()           // map[string]string{"q": "golang", "page": "2"}

Form Data

c.FormValue(key string) string
c.FormValueDefault(key, defaultValue string) string

Returns form parameter from POST request body.

// POST with form data
username := c.FormValue("username")
role := c.FormValueDefault("role", "user")

Headers

c.Request.Header.Get(key string) string
c.RequestHeaders() map[string]string
c.ResponseHeaders() map[string]string

Request Binding

Strict JSON Binding

c.BindStrict(dst any, opt BindOptions) error

Binds JSON with strict validation:

  • Rejects unknown fields (catches typos)
  • Enforces size limits
  • Distinguishes 400 (malformed) vs 422 (type errors)
var req CreateUserRequest
if err := c.BindStrict(&req, router.BindOptions{MaxBytes: 1 << 20}); err != nil {
    return // Error response already written
}

Content Type Validation

c.RequireContentType(allowed ...string) bool
c.RequireContentTypeJSON() bool
if !c.RequireContentTypeJSON() {
    return // 415 Unsupported Media Type already sent
}

Streaming

// Stream JSON array items
router.StreamJSONArray[T](c *Context, each func(T) error, maxItems int) error

// Stream NDJSON (newline-delimited JSON)
router.StreamNDJSON[T](c *Context, each func(T) error) error
err := router.StreamJSONArray(c, func(item User) error {
    return processUser(item)
}, 10000) // Max 10k items

Response Methods

JSON Responses

c.JSON(code int, obj any) error
c.IndentedJSON(code int, obj any) error
c.PureJSON(code int, obj any) error      // No HTML escaping
c.SecureJSON(code int, obj any, prefix ...string) error
c.ASCIIJSON(code int, obj any) error     // All non-ASCII escaped

Other Formats

c.YAML(code int, obj any) error
c.String(code int, value string) error
c.Stringf(code int, format string, values ...any) error
c.HTML(code int, html string) error

Binary & Streaming

c.Data(code int, contentType string, data []byte) error
c.DataFromReader(code int, contentLength int64, contentType string, reader io.Reader, extraHeaders map[string]string) error

File Serving

c.ServeFile(filepath string)

Status & No Content

c.Status(code int)
c.NoContent()

Error Responses

c.WriteErrorResponse(status int, message string)
c.NotFound()
c.MethodNotAllowed(allowed []string)

Headers

c.Header(key, value string)

Sets a response header with automatic security sanitization (newlines stripped).

URL Information

c.Hostname() string    // Host without port
c.Port() string        // Port number
c.Scheme() string      // "http" or "https"
c.BaseURL() string     // scheme + host
c.FullURL() string     // Complete URL with query string

Client Information

c.ClientIP() string      // Real client IP (respects trusted proxies)
c.ClientIPs() []string   // All IPs from X-Forwarded-For chain
c.IsHTTPS() bool         // Request over HTTPS
c.IsLocalhost() bool     // Request from localhost
c.IsXHR() bool           // XMLHttpRequest (AJAX)
c.Subdomains(offset ...int) []string

Content Type Detection

c.IsJSON() bool      // Content-Type is application/json
c.IsXML() bool       // Content-Type is application/xml or text/xml
c.AcceptsJSON() bool // Accept header includes application/json
c.AcceptsHTML() bool // Accept header includes text/html

Content Negotiation

c.Accepts(offers ...string) string
c.AcceptsCharsets(offers ...string) string
c.AcceptsEncodings(offers ...string) string
c.AcceptsLanguages(offers ...string) string
// Accept: application/json, text/html;q=0.9
best := c.Accepts("json", "html", "xml") // "json"

// Accept-Language: en-US, fr;q=0.8
lang := c.AcceptsLanguages("en", "fr", "de") // "en"

Caching

c.IsFresh() bool  // Response still fresh in client cache
c.IsStale() bool  // Client cache is stale
if c.IsFresh() {
    c.Status(http.StatusNotModified) // 304
    return
}

Redirects

c.Redirect(code int, location string)
c.Redirect(http.StatusFound, "/login")
c.Redirect(http.StatusMovedPermanently, "https://newdomain.com")

Cookies

c.SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool)
c.GetCookie(name string) (string, error)

File Uploads

c.File(name string) (*File, error)
c.Files(name string) ([]*File, error)

File methods:

file.Bytes() ([]byte, error)
file.Open() (io.ReadCloser, error)
file.Save(dst string) error
file.Ext() string
file, err := c.File("avatar")
if err != nil {
    return c.JSON(400, map[string]string{"error": "avatar required"})
}
file.Save("./uploads/" + uuid.New().String() + file.Ext())

Middleware Control

c.Next()           // Execute next handler in chain
c.Abort()          // Stop handler chain
c.IsAborted() bool // Check if chain was aborted

Error Collection

c.Error(err error)      // Collect error without writing response
c.Errors() []error      // Get all collected errors
c.HasErrors() bool      // Check if errors were collected
if err := validateUser(c); err != nil {
    c.Error(err)
}
if err := validateEmail(c); err != nil {
    c.Error(err)
}

if c.HasErrors() {
    c.JSON(400, map[string]any{"errors": c.Errors()})
    return
}

Context Access

c.RequestContext() context.Context  // Request's context.Context
c.TraceContext() context.Context    // OpenTelemetry trace context
c.Logger() *slog.Logger             // Request-scoped logger

Tracing & Metrics

Tracing

c.TraceID() string
c.SpanID() string
c.Span() trace.Span
c.SetSpanAttribute(key string, value any)
c.AddSpanEvent(name string, attrs ...attribute.KeyValue)

Metrics

c.RecordMetric(name string, value float64, attributes ...attribute.KeyValue)
c.IncrementCounter(name string, attributes ...attribute.KeyValue)
c.SetGauge(name string, value float64, attributes ...attribute.KeyValue)

Versioning

c.Version() string           // Current API version ("v1", "v2", etc.)
c.IsVersion(version string) bool
c.RoutePattern() string      // Matched route pattern ("/users/:id")

Complete Example

func handler(c *router.Context) {
    // Parameters
    id := c.Param("id")
    query := c.Query("q")
    
    // Headers
    auth := c.Request.Header.Get("Authorization")
    c.Header("X-Custom", "value")
    
    // Strict binding (for full binding, use binding package)
    var req CreateRequest
    if err := c.BindStrict(&req, router.BindOptions{MaxBytes: 1 << 20}); err != nil {
        return // Error response already written
    }
    
    // Tracing
    c.SetSpanAttribute("user.id", id)
    
    // Logging
    c.Logger().Info("processing request", "user_id", id)
    
    // Response
    if err := c.JSON(200, map[string]string{
        "id":    id,
        "query": query,
    }); err != nil {
        c.Logger().Error("failed to write response", "error", err)
    }
}

Next Steps

4 - Route Constraints

Type-safe parameter validation with route constraints.

Route constraints provide parameter validation that maps to OpenAPI schema types.

Typed Constraints

WhereInt(param string) *Route

Validates parameter as integer (OpenAPI: type: integer, format: int64).

r.GET("/users/:id", getUserHandler).WhereInt("id")

Matches:

  • /users/123
  • /users/abc

WhereFloat(param string) *Route

Validates parameter as float (OpenAPI: type: number, format: double).

r.GET("/prices/:amount", getPriceHandler).WhereFloat("amount")

Matches:

  • /prices/19.99
  • /prices/abc

WhereUUID(param string) *Route

Validates parameter as UUID (OpenAPI: type: string, format: uuid).

r.GET("/entities/:uuid", getEntityHandler).WhereUUID("uuid")

Matches:

  • /entities/550e8400-e29b-41d4-a716-446655440000
  • /entities/not-a-uuid

WhereDate(param string) *Route

Validates parameter as date (OpenAPI: type: string, format: date).

r.GET("/orders/:date", getOrderHandler).WhereDate("date")

Matches:

  • /orders/2024-01-18
  • /orders/invalid-date

WhereDateTime(param string) *Route

Validates parameter as date-time (OpenAPI: type: string, format: date-time).

r.GET("/events/:timestamp", getEventHandler).WhereDateTime("timestamp")

Matches:

  • /events/2024-01-18T10:30:00Z
  • /events/invalid

WhereEnum(param string, values ...string) *Route

Validates parameter against enum values (OpenAPI: enum).

r.GET("/status/:state", getStatusHandler).WhereEnum("state", "active", "pending", "deleted")

Matches:

  • /status/active
  • /status/invalid

Regex Constraints

WhereRegex(param, pattern string) *Route

Custom regex validation (OpenAPI: pattern).

// Alphanumeric only
r.GET("/slugs/:slug", getSlugHandler).WhereRegex("slug", `[a-zA-Z0-9]+`)

// Email validation
r.GET("/users/:email", getUserByEmailHandler).WhereRegex("email", `[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}`)

Multiple Constraints

Apply multiple constraints to the same route:

r.GET("/posts/:id/:slug", getPostHandler).
    WhereInt("id").
    WhereRegex("slug", `[a-zA-Z0-9-]+`)

Common Patterns

RESTful IDs

// Integer IDs
r.GET("/users/:id", getUserHandler).WhereInt("id")
r.PUT("/users/:id", updateUserHandler).WhereInt("id")
r.DELETE("/users/:id", deleteUserHandler).WhereInt("id")

// UUID IDs
r.GET("/entities/:uuid", getEntityHandler).WhereUUID("uuid")

Slugs and Identifiers

// Alphanumeric slugs
r.GET("/posts/:slug", getPostBySlugHandler).WhereRegex("slug", `[a-z0-9-]+`)

// Category identifiers
r.GET("/categories/:name", getCategoryHandler).WhereRegex("name", `[a-zA-Z0-9_-]+`)

Status and States

// Enum validation for states
r.GET("/orders/:status", getOrdersByStatusHandler).WhereEnum("status", "pending", "processing", "shipped", "delivered")

Complete Example

package main

import (
    "net/http"
    "rivaas.dev/router"
)

func main() {
    r := router.New()
    
    // Integer constraint
    r.GET("/users/:id", getUserHandler).WhereInt("id")
    
    // UUID constraint
    r.GET("/entities/:uuid", getEntityHandler).WhereUUID("uuid")
    
    // Enum constraint
    r.GET("/status/:state", getStatusHandler).WhereEnum("state", "active", "inactive", "pending")
    
    // Regex constraint
    r.GET("/posts/:slug", getPostHandler).WhereRegex("slug", `[a-z0-9-]+`)
    
    // Multiple constraints
    r.GET("/articles/:id/:slug", getArticleHandler).
        WhereInt("id").
        WhereRegex("slug", `[a-z0-9-]+`)
    
    http.ListenAndServe(":8080", r)
}

func getUserHandler(c *router.Context) {
    c.JSON(200, map[string]string{"user_id": c.Param("id")})
}

func getEntityHandler(c *router.Context) {
    c.JSON(200, map[string]string{"uuid": c.Param("uuid")})
}

func getStatusHandler(c *router.Context) {
    c.JSON(200, map[string]string{"state": c.Param("state")})
}

func getPostHandler(c *router.Context) {
    c.JSON(200, map[string]string{"slug": c.Param("slug")})
}

func getArticleHandler(c *router.Context) {
    c.JSON(200, map[string]string{
        "id":   c.Param("id"),
        "slug": c.Param("slug"),
    })
}

Next Steps

5 - Middleware Reference

Built-in middleware catalog with configuration options.

The router includes production-ready middleware in sub-packages. Each middleware uses functional options for configuration.

Security

Security Headers

Package: rivaas.dev/router/middleware/security

import "rivaas.dev/router/middleware/security"

r.Use(security.New(
    security.WithHSTS(true),
    security.WithFrameDeny(true),
    security.WithContentTypeNosniff(true),
    security.WithXSSProtection(true),
))

CORS

Package: rivaas.dev/router/middleware/cors

import "rivaas.dev/router/middleware/cors"

r.Use(cors.New(
    cors.WithAllowedOrigins("https://example.com"),
    cors.WithAllowedMethods("GET", "POST", "PUT", "DELETE"),
    cors.WithAllowedHeaders("Content-Type", "Authorization"),
    cors.WithAllowCredentials(true),
    cors.WithMaxAge(3600),
))

Basic Auth

Package: rivaas.dev/router/middleware/basicauth

import "rivaas.dev/router/middleware/basicauth"

admin := r.Group("/admin")
admin.Use(basicauth.New(
    basicauth.WithCredentials("admin", "secret"),
    basicauth.WithRealm("Admin Area"),
))

Observability

Access Log

Package: rivaas.dev/router/middleware/accesslog

import (
    "log/slog"
    "rivaas.dev/router/middleware/accesslog"
)

logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
r.Use(accesslog.New(
    accesslog.WithLogger(logger),
    accesslog.WithExcludePaths("/health", "/metrics"),
    accesslog.WithSampleRate(0.1),
    accesslog.WithSlowThreshold(500 * time.Millisecond),
))

Request ID

Package: rivaas.dev/router/middleware/requestid

Generates unique, time-ordered request IDs for distributed tracing and log correlation.

import "rivaas.dev/router/middleware/requestid"

// UUID v7 by default (36 chars, time-ordered, RFC 9562)
r.Use(requestid.New())

// Use ULID for shorter IDs (26 chars)
r.Use(requestid.New(requestid.WithULID()))

// Custom header name
r.Use(requestid.New(requestid.WithHeader("X-Correlation-ID")))

// Get request ID in handlers
func handler(c *router.Context) {
    id := requestid.Get(c)
}

ID Formats:

  • UUID v7 (default): 018f3e9a-1b2c-7def-8000-abcdef123456
  • ULID: 01ARZ3NDEKTSV4RRFFQ69G5FAV

Reliability

Recovery

Package: rivaas.dev/router/middleware/recovery

import "rivaas.dev/router/middleware/recovery"

r.Use(recovery.New(
    recovery.WithPrintStack(true),
    recovery.WithLogger(logger),
))

Timeout

Package: rivaas.dev/router/middleware/timeout

import "rivaas.dev/router/middleware/timeout"

r.Use(timeout.New(
    timeout.WithDuration(30 * time.Second),
    timeout.WithMessage("Request timeout"),
))

Rate Limit

Package: rivaas.dev/router/middleware/ratelimit

import "rivaas.dev/router/middleware/ratelimit"

r.Use(ratelimit.New(
    ratelimit.WithRequestsPerSecond(1000),
    ratelimit.WithBurst(100),
    ratelimit.WithKeyFunc(func(c *router.Context) string {
        return c.ClientIP() // Rate limit by IP
    }),
    ratelimit.WithLogger(logger),
))

Body Limit

Package: rivaas.dev/router/middleware/bodylimit

import "rivaas.dev/router/middleware/bodylimit"

r.Use(bodylimit.New(
    bodylimit.WithLimit(10 * 1024 * 1024), // 10MB
))

Performance

Compression

Package: rivaas.dev/router/middleware/compression

import "rivaas.dev/router/middleware/compression"

r.Use(compression.New(
    compression.WithLevel(compression.DefaultCompression),
    compression.WithMinSize(1024), // Don't compress <1KB
    compression.WithLogger(logger),
))

Other

Method Override

Package: rivaas.dev/router/middleware/methodoverride

import "rivaas.dev/router/middleware/methodoverride"

r.Use(methodoverride.New(
    methodoverride.WithHeader("X-HTTP-Method-Override"),
))

Trailing Slash

Package: rivaas.dev/router/middleware/trailingslash

import "rivaas.dev/router/middleware/trailingslash"

r.Use(trailingslash.New(
    trailingslash.WithRedirectCode(301),
))

Middleware Ordering

Recommended middleware order:

r := router.New()

// 1. Request ID
r.Use(requestid.New())

// 2. AccessLog
r.Use(accesslog.New())

// 3. Recovery
r.Use(recovery.New())

// 4. Security/CORS
r.Use(security.New())
r.Use(cors.New())

// 5. Body Limit
r.Use(bodylimit.New())

// 6. Rate Limit
r.Use(ratelimit.New())

// 7. Timeout
r.Use(timeout.New())

// 8. Authentication
r.Use(auth.New())

// 9. Compression (last)
r.Use(compression.New())

Complete Example

package main

import (
    "log/slog"
    "net/http"
    "os"
    "time"
    
    "rivaas.dev/router"
    "rivaas.dev/router/middleware/accesslog"
    "rivaas.dev/router/middleware/cors"
    "rivaas.dev/router/middleware/recovery"
    "rivaas.dev/router/middleware/requestid"
    "rivaas.dev/router/middleware/security"
)

func main() {
    logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
    r := router.New()
    
    // Observability
    r.Use(requestid.New())
    r.Use(accesslog.New(
        accesslog.WithLogger(logger),
        accesslog.WithExcludePaths("/health"),
    ))
    
    // Reliability
    r.Use(recovery.New())
    
    // Security
    r.Use(security.New())
    r.Use(cors.New(
        cors.WithAllowedOrigins("*"),
        cors.WithAllowedMethods("GET", "POST", "PUT", "DELETE"),
    ))
    
    r.GET("/", func(c *router.Context) {
        c.JSON(200, map[string]string{"message": "Hello"})
    })
    
    http.ListenAndServe(":8080", r)
}

Next Steps

6 - Diagnostics

Diagnostic event types and handling.

The router emits optional diagnostic events for security concerns and configuration issues.

Event Types

DiagXFFSuspicious

Suspicious X-Forwarded-For chain detected (>10 IPs).

Fields:

  • chain (string) - The full X-Forwarded-For header value
  • count (int) - Number of IPs in the chain
handler := router.DiagnosticHandlerFunc(func(e router.DiagnosticEvent) {
    if e.Kind == router.DiagXFFSuspicious {
        log.Printf("Suspicious XFF chain: %s (count: %d)", 
            e.Fields["chain"], e.Fields["count"])
    }
})

DiagHeaderInjection

Header injection attempt blocked and sanitized.

Fields:

  • header (string) - Header name
  • value (string) - Original value
  • sanitized (string) - Sanitized value

DiagInvalidProto

Invalid X-Forwarded-Proto value.

Fields:

  • proto (string) - Invalid protocol value

DiagHighParamCount

Route has >8 parameters (uses map storage instead of array).

Fields:

  • method (string) - HTTP method
  • path (string) - Route path
  • param_count (int) - Number of parameters

DiagH2CEnabled

H2C enabled (development warning).

Fields:

  • None

Enabling Diagnostics

import "log/slog"

handler := router.DiagnosticHandlerFunc(func(e router.DiagnosticEvent) {
    slog.Warn(e.Message, "kind", e.Kind, "fields", e.Fields)
})

r := router.New(router.WithDiagnostics(handler))

Handler Examples

With Logging

import "log/slog"

handler := router.DiagnosticHandlerFunc(func(e router.DiagnosticEvent) {
    slog.Warn(e.Message, 
        "kind", e.Kind, 
        "fields", e.Fields,
    )
})

With Metrics

handler := router.DiagnosticHandlerFunc(func(e router.DiagnosticEvent) {
    metrics.Increment("router.diagnostics", 
        "kind", string(e.Kind),
    )
})

With OpenTelemetry

import (
    "go.opentelemetry.io/otel/attribute"
    "go.opentelemetry.io/otel/trace"
)

handler := router.DiagnosticHandlerFunc(func(e router.DiagnosticEvent) {
    span := trace.SpanFromContext(ctx)
    if span.IsRecording() {
        attrs := []attribute.KeyValue{
            attribute.String("diagnostic.kind", string(e.Kind)),
        }
        for k, v := range e.Fields {
            attrs = append(attrs, attribute.String(k, fmt.Sprint(v)))
        }
        span.AddEvent(e.Message, trace.WithAttributes(attrs...))
    }
})

Complete Example

package main

import (
    "log/slog"
    "net/http"
    "os"
    
    "rivaas.dev/router"
)

func main() {
    logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
    
    // Diagnostic handler
    diagHandler := router.DiagnosticHandlerFunc(func(e router.DiagnosticEvent) {
        logger.Warn(e.Message,
            "kind", e.Kind,
            "fields", e.Fields,
        )
    })
    
    // Create router with diagnostics
    r := router.New(router.WithDiagnostics(diagHandler))
    
    r.GET("/", func(c *router.Context) {
        c.JSON(200, map[string]string{"message": "Hello"})
    })
    
    http.ListenAndServe(":8080", r)
}

Best Practices

  1. Log diagnostic events for security monitoring
  2. Track metrics for diagnostic event frequency
  3. Alert on suspicious patterns (e.g., repeated XFF warnings)
  4. Don’t ignore warnings - they indicate potential issues

Next Steps

7 - Troubleshooting

Common issues and solutions for the router package.

This guide helps you troubleshoot common issues with the Rivaas Router.

Quick Reference

IssueSolutionExample
404 Route Not FoundCheck route syntax and order.r.GET("/users/:id", handler)
Middleware Not RunningRegister before routes.r.Use(middleware); r.GET("/path", handler)
Parameters Not WorkingUse :param syntax.r.GET("/users/:id", handler)
CORS IssuesAdd CORS middleware.r.Use(cors.New())
Memory LeaksDon’t store context references.Extract data immediately.
Slow PerformanceUse route groups.api := r.Group("/api")

Common Issues

Route Not Found (404 errors)

Problem: Routes not matching as expected.

Solutions:

// ✅ Correct: Use :param syntax
r.GET("/users/:id", handler)

// ❌ Wrong: Don't use {param} syntax
r.GET("/users/{id}", handler)

// ✅ Correct: Static route
r.GET("/users/me", currentUserHandler)

// Check route registration order
r.GET("/users/me", currentUserHandler)      // Register specific routes first
r.GET("/users/:id", getUserHandler)         // Then parameter routes

Middleware Not Executing

Problem: Middleware doesn’t run for routes.

Solution: Register middleware before routes.

// ✅ Correct: Middleware before routes
r.Use(Logger())
r.GET("/api/users", handler)

// ❌ Wrong: Routes before middleware
r.GET("/api/users", handler)
r.Use(Logger()) // Too late!

// ✅ Correct: Group middleware
api := r.Group("/api")
api.Use(Auth())
api.GET("/users", handler)

Parameter Constraints Not Working

Problem: Invalid parameters still match routes.

Solution: Apply constraints to routes.

// ✅ Correct: Integer constraint
r.GET("/users/:id", handler).WhereInt("id")

// ✅ Correct: Custom regex
r.GET("/files/:name", handler).WhereRegex("name", `[a-zA-Z0-9.-]+`)

// ❌ Wrong: No constraint (matches anything)
r.GET("/users/:id", handler) // Matches "/users/abc"

Memory Leaks

Problem: Growing memory usage.

Solution: Never store Context references.

// ❌ Wrong: Storing context
var globalContext *router.Context
func handler(c *router.Context) {
    globalContext = c // Memory leak!
}

// ✅ Correct: Extract data immediately
func handler(c *router.Context) {
    userID := c.Param("id")
    // Use userID, not c
    processUser(userID)
}

// ✅ Correct: Copy data for async operations
func handler(c *router.Context) {
    userID := c.Param("id")
    go func(id string) {
        processAsync(id)
    }(userID)
}

CORS Issues

Problem: CORS errors in browser.

Solution: Add CORS middleware.

import "rivaas.dev/router/middleware/cors"

r.Use(cors.New(
    cors.WithAllowedOrigins("https://example.com"),
    cors.WithAllowedMethods("GET", "POST", "PUT", "DELETE"),
    cors.WithAllowedHeaders("Content-Type", "Authorization"),
))

Slow Performance

Problem: Routes are slow.

Solutions:

// ✅ Use route groups
api := r.Group("/api")
api.GET("/users", handler)
api.GET("/posts", handler)

// ✅ Minimize middleware
r.Use(Recovery()) // Essential only

// ✅ Apply constraints for parameter validation
r.GET("/users/:id", handler).WhereInt("id")

// ❌ Don't parse parameters manually
func handler(c *router.Context) {
    // id, err := strconv.Atoi(c.Param("id")) // Slow
    id := c.Param("id") // Fast
}

Validation Errors

Problem: Validation not working.

Solutions:

// ✅ Register custom tags in init()
func init() {
    router.RegisterTag("custom", validatorFunc)
}

// ✅ Use correct strategy
func createUser(c *router.Context) {
    var req CreateUserRequest
    if !c.MustBindAndValidate(&req) {
        return
    }
}

// ✅ Partial validation for PATCH
func updateUser(c *router.Context) {
    var req UpdateUserRequest
    if !c.MustBindAndValidate(&req, router.WithPartial(true)) {
        return
    }
}

FAQ

Can I use standard HTTP middleware?

Yes! Adapt existing middleware:

func adaptMiddleware(next http.Handler) router.HandlerFunc {
    return func(c *router.Context) {
        next.ServeHTTP(c.Writer, c.Request)
    }
}

Is the router production-ready?

Yes. The router is production-ready with:

  • 84.8% code coverage
  • Comprehensive test suite
  • Zero race conditions
  • 8.4M+ req/s throughput

How do I handle CORS?

Use the built-in CORS middleware:

import "rivaas.dev/router/middleware/cors"

r.Use(cors.New(
    cors.WithAllowedOrigins("*"),
    cors.WithAllowedMethods("GET", "POST", "PUT", "DELETE"),
))

Why are my parameters not working?

Check the parameter syntax:

// ✅ Correct
r.GET("/users/:id", handler)
id := c.Param("id")

// ❌ Wrong syntax
r.GET("/users/{id}", handler) // Use :id instead

How do I debug routing issues?

Use route introspection:

routes := r.Routes()
for _, route := range routes {
    fmt.Printf("%s %s -> %s\n", route.Method, route.Path, route.HandlerName)
}

Getting Help

Next Steps