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
- 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
External Links
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
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.
Security Warning
Only use in development or behind a trusted load balancer. DO NOT enable on public-facing servers without TLS.
r := router.MustNew(router.WithH2C(true))
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
))
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
Note
For tracing, metrics, and logging configuration, use the app package which provides WithObservability(), WithTracing(), WithMetrics(), and WithLogging() options. These options configure the full observability stack and integrate with the router automatically.
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.
Memory Safety
Context objects are pooled and reused. Never store references to Context beyond the request handler. Check Context Guide for details.
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"}
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")
c.Request.Header.Get(key string) string
c.RequestHeaders() map[string]string
c.ResponseHeaders() map[string]string
Request Binding
Note
For full request binding (Bind, BindQuery, BindJSON, etc.), use the separate binding package. The router Context provides only strict JSON 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
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)
c.Header(key, value string)
Sets a response header with automatic security sanitization (newlines stripped).
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
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:
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
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
))
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 valuecount (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"])
}
})
Header injection attempt blocked and sanitized.
Fields:
header (string) - Header namevalue (string) - Original valuesanitized (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 methodpath (string) - Route pathparam_count (int) - Number of parameters
DiagH2CEnabled
H2C enabled (development warning).
Fields:
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
- Log diagnostic events for security monitoring
- Track metrics for diagnostic event frequency
- Alert on suspicious patterns (e.g., repeated XFF warnings)
- 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
| Issue | Solution | Example |
|---|
| 404 Route Not Found | Check route syntax and order. | r.GET("/users/:id", handler) |
| Middleware Not Running | Register before routes. | r.Use(middleware); r.GET("/path", handler) |
| Parameters Not Working | Use :param syntax. | r.GET("/users/:id", handler) |
| CORS Issues | Add CORS middleware. | r.Use(cors.New()) |
| Memory Leaks | Don’t store context references. | Extract data immediately. |
| Slow Performance | Use 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"),
))
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