app

package module
v0.21.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 24, 2026 License: Apache-2.0 Imports: 44 Imported by: 0

README

App

Go Reference Go Report Card Coverage Go Version License

A batteries-included web framework built on the Rivaas router with integrated observability, lifecycle management, and sensible defaults for building production-ready applications.

📚 Documentation

📖 Full Documentation | 🔧 Installation | 📘 User Guide | 📑 API Reference | 💡 Examples | 🐛 Troubleshooting

Features

  • Batteries-Included - Pre-configured with sensible defaults for rapid development
  • Integrated Observability - Built-in metrics (Prometheus/OTLP), tracing (OpenTelemetry), and structured logging (slog)
  • Request Binding & Validation – Automatic request parsing with comprehensive validation strategies
  • OpenAPI Generation – Automatic OpenAPI spec generation with Swagger UI
  • Lifecycle Hooks - OnStart, OnReady, OnShutdown, OnStop for initialization and cleanup
  • Health Endpoints - Kubernetes-compatible liveness and readiness probes
  • Graceful Shutdown – Proper server shutdown with configurable timeouts
  • Environment-Aware - Development and production modes with appropriate defaults

Installation

go get rivaas.dev/app

Requires Go 1.25+

Quick Start

package main

import (
    "context"
    "log"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    
    "rivaas.dev/app"
)

func main() {
    a, err := app.New()
    if err != nil {
        log.Fatal(err)
    }

    a.GET("/", func(c *app.Context) {
        c.JSON(http.StatusOK, map[string]string{
            "message": "Hello from Rivaas App!",
        })
    })

    ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
    defer cancel()

    if err := a.Start(ctx); err != nil {
        log.Fatal(err)
    }
}

For HTTPS or mTLS, configure at construction with WithTLS or WithMTLS, then call Start(ctx). The default port is 8080 for HTTP and 8443 for TLS/mTLS (override with WithPort).

Custom tracing: In handlers use c.SetSpanAttribute, c.AddSpanEvent, and for child spans c.StartSpan("name") with defer c.FinishSpan(span, statusCode). Use c.Tracer() only for advanced use (e.g., passing the tracer to another library). Request spans support W3C propagation and sampling.

Custom metrics: Use c.IncrementCounter, c.AddCounter, c.RecordHistogram, and c.SetGauge on app.Context.

Learn More

API Reference

See pkg.go.dev/rivaas.dev/app for complete API documentation.

Contributing

Contributions are welcome! Please see the main repository for contribution guidelines.

License

Apache License 2.0 – see LICENSE for details.


Part of the Rivaas web framework ecosystem.

Documentation

Overview

Package app provides a batteries-included, cloud-native web framework built on top of the Rivaas router. It features high-performance routing, comprehensive request binding & validation, automatic OpenAPI generation, and OpenTelemetry-native observability, along with lifecycle management and sensible defaults for building production-ready web applications.

Overview

The app package wraps the router package with additional features:

  • Integrated observability (metrics, tracing, logging)
  • Lifecycle hooks (OnStart, OnReady, OnShutdown, OnStop)
  • Graceful shutdown handling
  • Server configuration management
  • Request binding and validation
  • OpenAPI/Swagger documentation with ETag-based caching
  • Health check endpoints
  • Development and production modes

When to Use

Use the app package when:

  • Building a complete web application with batteries included
  • You want integrated observability configured out of the box
  • You need development with sensible defaults
  • Building REST APIs with common middleware patterns
  • You prefer convention over configuration

Use the router package directly when:

  • Building a library or framework that needs full control
  • You have custom observability setup already configured
  • You need complete flexibility without any opinions
  • Integrating into existing systems with established patterns

Constructor Pattern

The app package follows the functional options pattern used throughout Rivaas:

  • Options apply to an internal config struct (not the App type directly)

  • New() validates the config and builds the App from the validated config

  • New() returns (*App, error) because app initialization can fail. The app initializes external resources (metrics, tracing, logging) that may fail to connect to backends, validate configurations, or allocate resources.

  • MustNew() is provided as a convenience wrapper that panics on error. This follows the standard Go idiom (like regexp.MustCompile, template.Must) and is useful for initialization in main() functions where errors should abort startup.

  • All configuration options use the "With" prefix for consistency.

  • Grouping options (e.g., WithServer, WithRouter) accept sub-options to organize related settings and reduce API surface.

Option Validation

Options must not be nil. Passing a nil option to New(), MustNew(), or methods that accept options (e.g., Test) returns a validation error, not a panic. This applies to both top-level options (e.g., WithServer) and nested options (e.g., WithReadTimeout inside WithServer).

Quick Start

Simple application with defaults:

app, err := app.New()
if err != nil {
    log.Fatal(err)
}

app.GET("/", func(c *app.Context) {
    c.JSON(http.StatusOK, map[string]string{"message": "Hello"})
})

ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
defer cancel()

if err := app.Start(ctx); err != nil {
    log.Fatal(err)
}

Full-featured application with observability, health, and debug endpoints:

app, err := app.New(
    app.WithServiceName("my-api"),
    app.WithServiceVersion("v1.0.0"),
    app.WithEnvironment("production"),
    // Observability: all three pillars in one place
    app.WithObservability(
        app.WithLogging(logging.WithJSONHandler()),
        app.WithMetrics(), // Prometheus is default
        app.WithTracing(tracing.WithOTLP("localhost:4317")),
    ),
    // Health endpoints: /livez and /readyz
    app.WithHealthEndpoints(
        app.WithLivenessCheck("process", func(ctx context.Context) error {
            return nil
        }),
        app.WithReadinessCheck("database", func(ctx context.Context) error {
            return db.PingContext(ctx)
        }),
    ),
    // Debug endpoints: /debug/pprof/* (conditionally enabled)
    app.WithDebugEndpoints(
        app.WithPprofIf(os.Getenv("PPROF_ENABLED") == "true"),
    ),
    app.WithServer(
        app.WithReadTimeout(15 * time.Second),
        app.WithWriteTimeout(15 * time.Second),
    ),
)
if err != nil {
    log.Fatal(err)
}

Or using MustNew for initialization that panics on error:

app := app.MustNew(
    app.WithServiceName("my-service"),
    app.WithObservability(
        app.WithMetrics(), // Prometheus is default
    ),
)

Observability

The app package integrates three pillars of observability:

  • Metrics: Prometheus-compatible metrics with automatic HTTP request instrumentation
  • Tracing: OpenTelemetry tracing with request context propagation
  • Logging: Structured logging with slog, including request-scoped fields

Request spans use the same semantics as the tracing package: W3C trace context extraction, sampling (e.g. WithSampleRate), and standard HTTP attributes (http.method, http.url, http.route, http.status_code, service.name, etc.).

From handlers, use Context.SetSpanAttribute and Context.AddSpanEvent on the current span. For child spans (e.g. "db-query", "call-service"), use Context.StartSpan and Context.FinishSpan for success, or Context.FinishSpanWithHTTPStatus when you have an HTTP status, or Context.FinishSpanWithError when the span fails. Use Context.RecordError to record an error on the request span without ending it. Use Context.CopyTraceContext to get a context for goroutines that share the same trace. Use Context.WithSpan to run a function under a span that is finished from the returned error:

ctx, span := c.StartSpan("db-query")
defer c.FinishSpan(span)
// or: defer func() { if err != nil { c.FinishSpanWithError(span, err) } else { c.FinishSpan(span) } }()
// or: err := c.WithSpan("fetch-user", func(ctx context.Context) error { ... })

Use Context.Tracer only when you need to pass the tracer to another library (e.g. DB driver, HTTP client) or use tracer-specific options (inject/extract).

For custom metrics from handlers, use Context.RecordHistogram, Context.IncrementCounter, Context.AddCounter, and Context.SetGauge:

c.IncrementCounter("requests_total")
c.AddCounter("bytes_processed", 1024, attribute.String("type", "upload"))
c.RecordHistogram("duration_seconds", 0.5)
c.SetGauge("queue_size", 10)

All observability features are optional and can be enabled independently:

app.New(
    app.WithObservability(
        app.WithMetrics(), // Prometheus is default; use metrics.WithOTLP() for OTLP
        app.WithTracing(tracing.WithOTLP("localhost:4317")),
        app.WithLogging(logging.WithJSONHandler()),
    ),
)

Lifecycle Hooks

The app provides lifecycle hooks for application events:

  • OnStart: Called before server starts (sequential, stops on first error)
  • OnReady: Called when server is ready to accept connections (async, non-blocking)
  • OnShutdown: Called during graceful shutdown (LIFO order)
  • OnStop: Called after shutdown completes (best-effort)

Example:

if err := app.OnStart(func(ctx context.Context) error {
    return db.Connect(ctx)
}); err != nil {
    log.Fatal(err)
}

if err := app.OnReady(func() {
    log.Println("Server is ready!")
}); err != nil {
    log.Fatal(err)
}

if err := app.OnShutdown(func(ctx context.Context) {
    db.Close()
}); err != nil {
    log.Fatal(err)
}

Request Handling

Handlers receive an app.Context that extends router.Context with app-level features:

  • Request binding (JSON, form, query parameters)
  • Request validation
  • Access to observability (metrics, tracing, logging)

The app uses a single handler type (app.HandlerFunc, which wraps router.HandlerFunc) for consistency across groups, version groups, and individual routes. This ensures the integration layer remains predictable and middleware composition is uniform.

Example:

app.POST("/users", func(c *app.Context) {
    var req CreateUserRequest
    if err := c.Bind(&req); err != nil {
        c.Fail(err)
        return
    }

    // Process request...
    c.JSON(http.StatusCreated, user)
})

Or using the type-safe generic API:

app.POST("/users", func(c *app.Context) {
    req, ok := app.MustBind[CreateUserRequest](c)
    if !ok {
        return // Error already written
    }

    // Process request...
    c.JSON(http.StatusCreated, user)
})

Request Binding and Validation

The app package provides a unified API for binding and validation:

Generic type-safe variants:

  • Bind: Type-safe binding with generics
  • MustBind: Type-safe Must pattern
  • BindOnly: Bind without validation (for advanced use)

Options for customization. Bind and Validate options must not be nil; passing a nil option returns an error.

Example with options:

app.PATCH("/users/:id", func(c *app.Context) {
    req, ok := app.MustBind[UpdateUserRequest](c, app.WithPartial())
    if !ok {
        return
    }
    // Only provided fields are validated
})

Server Configuration

Configure server address and timeouts using functional options:

app.New(
    app.WithPort(3000),                 // Listen on port 3000
    app.WithHost("127.0.0.1"),          // Bind to localhost only
    app.WithServer(
        app.WithReadTimeout(10 * time.Second),
        app.WithWriteTimeout(10 * time.Second),
        app.WithIdleTimeout(60 * time.Second),
        app.WithShutdownTimeout(30 * time.Second),
    ),
)

For HTTPS use WithTLS(certFile, keyFile); for mTLS use WithMTLS(serverCert, ...MTLSOption). Then call Start(ctx). Default port is 8080 for HTTP and 8443 for TLS/mTLS; override with WithPort or RIVAAS_PORT. Configuration is automatically validated to catch common misconfigurations.

Environment Variables

The app package supports configuration via environment variables using WithEnv:

app.New(
    app.WithServiceName("orders-api"),
    app.WithEnv(),  // Enable RIVAAS_* environment variable overrides
)

Environment variables override programmatic configuration. Supported variables:

Core:
  RIVAAS_ENV                    - Environment mode: "development" or "production"
  RIVAAS_SERVICE_NAME           - Service name for observability
  RIVAAS_SERVICE_VERSION        - Service version

Server:
  RIVAAS_PORT                   - Server port (default 8080 for HTTP, 8443 for TLS/mTLS; e.g., "8080", "443")
  RIVAAS_HOST                   - HTTP server host/interface (e.g., "127.0.0.1")
  RIVAAS_READ_TIMEOUT           - Request read timeout (e.g., "10s")
  RIVAAS_WRITE_TIMEOUT          - Response write timeout (e.g., "10s")
  RIVAAS_SHUTDOWN_TIMEOUT       - Graceful shutdown timeout (e.g., "30s")

Logging:
  RIVAAS_LOG_LEVEL              - Log level: "debug", "info", "warn", "error"
  RIVAAS_LOG_FORMAT             - Log format: "json", "text", or "console"

Observability:
  RIVAAS_METRICS_EXPORTER       - Metrics exporter: "prometheus", "otlp", or "stdout"
  RIVAAS_METRICS_ADDR           - Prometheus address (default: ":9090")
  RIVAAS_METRICS_PATH           - Prometheus path (default: "/metrics")
  RIVAAS_METRICS_ENDPOINT       - OTLP metrics endpoint (e.g., "http://localhost:4318")
  RIVAAS_TRACING_EXPORTER       - Tracing exporter: "otlp", "otlp-http", or "stdout"
  RIVAAS_TRACING_ENDPOINT       - OTLP tracing endpoint (e.g., "localhost:4317")

Debug:
  RIVAAS_PPROF_ENABLED          - Enable pprof: "true" or "false"

Use WithEnvPrefix for a custom prefix:

app.New(
    app.WithEnvPrefix("MYAPP_"),  // Use MYAPP_* instead of RIVAAS_*
)

Invalid environment values cause New to return an error (fail-fast).

Examples

See the examples directory for complete working examples:

  • examples/01-quick-start: Minimal setup to get started (basic routing, JSON responses)
  • examples/02-blog: Real-world blog API with configuration, validation, OpenAPI docs, observability, and testing

Architecture

The app package is built on top of the router package:

┌─────────────────────────────────────────┐
│           Application Layer             │
│  (app package - this package)           │
│                                         │
│  • Configuration Management             │
│  • Lifecycle Hooks                      │
│  • Observability Integration            │
│  • Server Management                    │
│  • Request Binding/Validation           │
└──────────────┬──────────────────────────┘
               │
               ▼
┌─────────────────────────────────────────┐
│           Router Layer                  │
│  (router package)                       │
│                                         │
│  • HTTP Routing                         │
│  • Middleware Chain                     │
│  • Request Context                      │
│  • Path Parameters                      │
└──────────────┬──────────────────────────┘
               │
               ▼
┌─────────────────────────────────────────┐
│        Standard Library                 │
│  (net/http)                             │
└─────────────────────────────────────────┘
Example

Example demonstrates basic app usage.

package main

import (
	"fmt"
	"log"
	"net/http"

	"rivaas.dev/app"
)

func main() {
	a := app.MustNew()

	a.GET("/", func(c *app.Context) {
		if jsonErr := c.JSON(http.StatusOK, map[string]string{
			"message": "Hello, World!",
		}); jsonErr != nil {
			log.Printf("Failed to write response: %v", jsonErr)
		}
	})

	fmt.Println("App created successfully")
}
Output:
App created successfully
Example (HealthEndpoints)

Example_healthEndpoints demonstrates health check endpoint configuration.

package main

import (
	"context"
	"fmt"

	"rivaas.dev/app"
)

func main() {
	a := app.MustNew(
		app.WithServiceName("example-api"),
		app.WithHealthEndpoints(
			app.WithLivenessCheck("process", func(ctx context.Context) error {
				// Process is alive
				return nil
			}),
			app.WithReadinessCheck("database", func(ctx context.Context) error {
				// Check database connection
				// return db.PingContext(ctx)
				return nil
			}),
		),
	)

	fmt.Printf("Health endpoints configured: %s\n", a.ServiceName())
}
Output:
Health endpoints configured: example-api
Example (LifecycleHooks)

Example_lifecycleHooks demonstrates lifecycle hook registration.

package main

import (
	"context"
	"fmt"
	"log"

	"rivaas.dev/app"
)

func main() {
	a := app.MustNew()

	if err := a.OnStart(func(ctx context.Context) error {
		// Initialize database, run migrations, etc.
		fmt.Println("OnStart: Initializing...")
		return nil
	}); err != nil {
		log.Fatal(err)
	}

	if err := a.OnReady(func() {
		// Register with service discovery, warmup caches, etc.
		fmt.Println("OnReady: Server is ready")
	}); err != nil {
		log.Fatal(err)
	}

	if err := a.OnShutdown(func(ctx context.Context) {
		// Close connections, flush buffers, etc.
		fmt.Println("OnShutdown: Cleaning up...")
	}); err != nil {
		log.Fatal(err)
	}

	fmt.Println("Lifecycle hooks registered")
}
Output:
Lifecycle hooks registered
Example (Middleware)

Example_middleware demonstrates middleware usage.

package main

import (
	"fmt"
	"log"
	"net/http"

	"rivaas.dev/app"
)

func main() {
	a := app.MustNew()

	a.Use(func(c *app.Context) {
		// Add custom header
		c.Header("X-Custom", "value")
		c.Next()
	})

	a.GET("/test", func(c *app.Context) {
		if err := c.String(http.StatusOK, "ok"); err != nil {
			log.Printf("Failed to write response: %v", err)
		}
	})

	fmt.Println("Middleware registered")
}
Output:
Middleware registered
Example (PartialValidation)

Example_partialValidation demonstrates partial validation for PATCH endpoints using Bind with WithPartial.

package main

import (
	"fmt"
	"log"
	"net/http"

	"rivaas.dev/app"
)

func main() {
	a := app.MustNew()

	type UpdateUserRequest struct {
		Name  *string `json:"name" validate:"omitempty,min=3"`
		Email *string `json:"email" validate:"omitempty,email"`
	}

	a.PATCH("/users/:id", func(c *app.Context) {
		req, err := app.Bind[UpdateUserRequest](c, app.WithPartial())
		if err != nil {
			c.Fail(err)
			return
		}

		userID := c.Param("id")
		if jsonErr := c.JSON(http.StatusOK, map[string]string{
			"message": "User updated",
			"id":      userID,
		}); jsonErr != nil {
			log.Printf("Failed to write response: %v", jsonErr)
		}

		_ = req // Use req for update logic
	})

	fmt.Println("PATCH handler with partial validation registered")
}
Output:
PATCH handler with partial validation registered
Example (Routing)

Example_routing demonstrates route registration.

package main

import (
	"fmt"
	"log"
	"net/http"

	"rivaas.dev/app"
)

func main() {
	a := app.MustNew()

	a.GET("/users", func(c *app.Context) {
		if err := c.JSON(http.StatusOK, map[string]string{"users": "list"}); err != nil {
			log.Printf("Failed to write response: %v", err)
		}
	})

	a.POST("/users", func(c *app.Context) {
		if err := c.JSON(http.StatusCreated, map[string]string{"user": "created"}); err != nil {
			log.Printf("Failed to write response: %v", err)
		}
	})

	fmt.Println("Routes registered")
}
Output:
Routes registered
Example (StrictBinding)

Example_strictBinding demonstrates strict mode for typo detection using Bind with WithStrict.

package main

import (
	"fmt"
	"log"
	"net/http"

	"rivaas.dev/app"
)

func main() {
	a := app.MustNew()

	type CreateUserRequest struct {
		Name  string `json:"name" validate:"required,min=3"`
		Email string `json:"email" validate:"required,email"`
	}

	a.POST("/users", func(c *app.Context) {
		req, err := app.Bind[CreateUserRequest](c, app.WithStrict())
		if err != nil {
			c.Fail(err)
			return
		}

		if jsonErr := c.JSON(http.StatusCreated, map[string]string{
			"message": "User created",
			"name":    req.Name,
		}); jsonErr != nil {
			log.Printf("Failed to write response: %v", jsonErr)
		}
	})

	fmt.Println("Handler with strict binding registered")
}
Output:
Handler with strict binding registered
Example (Testing)

Example_testing demonstrates testing patterns.

package main

import (
	"fmt"
	"log"
	"net/http"
	"net/http/httptest"

	"rivaas.dev/app"
)

func main() {
	a := app.MustNew()

	a.GET("/health", func(c *app.Context) {
		if err := c.String(http.StatusOK, "ok"); err != nil {
			log.Printf("Failed to write response: %v", err)
		}
	})

	req := httptest.NewRequest(http.MethodGet, "/health", nil)
	resp, err := a.Test(req)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Status: %d\n", resp.StatusCode)
	//nolint:errcheck // example code, we don't care about the error here
	resp.Body.Close()
}
Output:
Status: 200
Example (WithObservability)

Example_withObservability demonstrates full observability setup.

package main

import (
	"fmt"

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

func main() {
	a := app.MustNew(
		app.WithServiceName("example-api"),
		app.WithServiceVersion("v1.0.0"),
		app.WithObservability(
			app.WithMetrics(metrics.WithPrometheus(":9090", "/metrics")),
			app.WithTracing(tracing.WithNoop()),
		),
	)

	fmt.Printf("Service: %s\n", a.ServiceName())
	fmt.Printf("Metrics: enabled\n")
}
Output:
Service: example-api
Metrics: enabled

Index

Examples

Constants

View Source
const (
	DefaultServiceName       = "rivaas-app"
	DefaultVersion           = "1.0.0"
	DefaultEnvironment       = "development"
	DefaultPort              = 8080
	DefaultTLSPort           = 8443 // Default port when serving TLS or mTLS (overridable with WithPort)
	DefaultReadTimeout       = 10 * time.Second
	DefaultWriteTimeout      = 10 * time.Second
	DefaultIdleTimeout       = 60 * time.Second
	DefaultReadHeaderTimeout = 2 * time.Second
	DefaultMaxHeaderBytes    = 1 << 20 // 1MB
	DefaultShutdownTimeout   = 30 * time.Second

	// Environment constants
	EnvironmentDevelopment = "development"
	EnvironmentProduction  = "production"
)

Default configuration values.

View Source
const (
	// Core application settings
	EnvMode           = "ENV"             // Environment mode: "development" or "production"
	EnvServiceName    = "SERVICE_NAME"    // Service name for observability
	EnvServiceVersion = "SERVICE_VERSION" // Service version

	// Server settings
	EnvPort            = "PORT"             // HTTP server port (e.g., "8080")
	EnvHost            = "HOST"             // HTTP server host/interface (e.g., "127.0.0.1")
	EnvReadTimeout     = "READ_TIMEOUT"     // Request read timeout (e.g., "10s")
	EnvWriteTimeout    = "WRITE_TIMEOUT"    // Response write timeout (e.g., "10s")
	EnvShutdownTimeout = "SHUTDOWN_TIMEOUT" // Graceful shutdown timeout (e.g., "30s")

	// Logging settings
	EnvLogLevel  = "LOG_LEVEL"  // Log level: "debug", "info", "warn", "error"
	EnvLogFormat = "LOG_FORMAT" // Log format: "json", "text", or "console"

	// Observability settings
	EnvMetricsExporter = "METRICS_EXPORTER" // Metrics exporter: "prometheus", "otlp", or "stdout"
	EnvMetricsAddr     = "METRICS_ADDR"     // Prometheus address (e.g., ":9090")
	EnvMetricsPath     = "METRICS_PATH"     // Prometheus path (e.g., "/metrics")
	EnvMetricsEndpoint = "METRICS_ENDPOINT" // OTLP endpoint (e.g., "http://localhost:4318")

	EnvTracingExporter = "TRACING_EXPORTER" // Tracing exporter: "otlp", "otlp-http", or "stdout"
	EnvTracingEndpoint = "TRACING_ENDPOINT" // OTLP endpoint (e.g., "localhost:4317")

	// Debug settings
	EnvPprofEnabled = "PPROF_ENABLED" // Enable pprof: "true" or "false"
)

Environment variable names for framework configuration. These are used when WithEnv or WithEnvPrefix is called.

View Source
const EnvPrefix = "RIVAAS_"

EnvPrefix is the environment variable prefix for Rivaas framework settings.

Variables

View Source
var ErrRouterFrozen = errors.New("cannot register hooks after router is frozen")

ErrRouterFrozen is returned when a lifecycle hook is registered after the router has been frozen (e.g. after Start() or Router().Freeze()). Register all hooks before starting the server. Use errors.Is(err, app.ErrRouterFrozen) to detect.

Functions

func Bind added in v0.9.0

func Bind[T any](c *Context, opts ...BindOption) (T, error)

Bind binds request data to type T and validates it. This is the recommended way to bind requests with type safety.

Bind automatically:

  • Detects Content-Type and binds from appropriate sources
  • Binds path, query, header, and cookie parameters based on struct tags
  • Validates the bound struct using the configured strategy
  • Tracks field presence for partial validation support

Errors:

Example:

req, err := app.Bind[CreateUserRequest](c)
if err != nil {
    c.Fail(err)
    return
}
// req is of type CreateUserRequest

With options (e.g. PATCH with partial validation, or strict unknown-field rejection):

req, err := app.Bind[CreateUserRequest](c, app.WithStrict())
req, err := app.Bind[UpdateUserRequest](c, app.WithPartial())
Example

ExampleBind demonstrates type-safe binding with generics.

package main

import (
	"fmt"
	"log"
	"net/http"

	"rivaas.dev/app"
)

func main() {
	a := app.MustNew()

	type CreateUserRequest struct {
		Name  string `json:"name" validate:"required,min=3"`
		Email string `json:"email" validate:"required,email"`
	}

	a.POST("/users", func(c *app.Context) {
		req, err := app.Bind[CreateUserRequest](c)
		if err != nil {
			c.Fail(err)
			return
		}

		if jsonErr := c.JSON(http.StatusCreated, map[string]string{
			"message": "User created",
			"name":    req.Name,
		}); jsonErr != nil {
			log.Printf("Failed to write response: %v", jsonErr)
		}
	})

	fmt.Println("Handler with generic Bind registered")
}
Output:
Handler with generic Bind registered

func BindOnly added in v0.9.0

func BindOnly[T any](c *Context, opts ...BindOption) (T, error)

BindOnly binds request data to type T without validation. Use when you need fine-grained control over the bind/validate lifecycle.

Example:

req, err := app.BindOnly[Request](c)
if err != nil {
    c.Fail(err)
    return
}
req.Normalize() // Custom processing
if err := c.Validate(&req); err != nil {
    c.Fail(err)
    return
}

func ExpectJSON

func ExpectJSON(t testingT, resp *http.Response, statusCode int, out any)

ExpectJSON is a test helper that asserts a response has the expected status code and JSON body. It decodes the JSON into the provided output value.

Example:

var user User
ExpectJSON(t, resp, 200, &user)
assert.Equal(t, "Alice", user.Name)

func GetMTLSCertificate

func GetMTLSCertificate(req *http.Request) *x509.Certificate

GetMTLSCertificate extracts the client certificate from an HTTP request. It returns the first peer certificate if available, or nil if the request is not using mTLS or no certificate is present.

It is useful for extracting principal information (e.g., CN, SAN) in handlers after the connection has been authorized via WithAuthorize.

Example:

func handler(c *router.Context) {
    cert := app.GetMTLSCertificate(c.Request)
    if cert != nil {
        principal := cert.Subject.CommonName
        // Use principal for authorization, logging, etc.
    }
}

func MustBind added in v0.9.0

func MustBind[T any](c *Context, opts ...BindOption) (T, bool)

MustBind binds and validates, writing an error response on failure. Returns the bound value and true if successful.

MustBind eliminates boilerplate error handling for the common case.

Example:

req, ok := app.MustBind[CreateUserRequest](c)
if !ok {
    return // Error already written
}
// req is of type CreateUserRequest
Example

ExampleMustBind demonstrates type-safe binding with the Must pattern.

package main

import (
	"fmt"
	"log"
	"net/http"

	"rivaas.dev/app"
)

func main() {
	a := app.MustNew()

	type CreateUserRequest struct {
		Name  string `json:"name" validate:"required,min=3"`
		Email string `json:"email" validate:"required,email"`
	}

	a.POST("/users", func(c *app.Context) {
		req, ok := app.MustBind[CreateUserRequest](c)
		if !ok {
			return // Error already written
		}

		if err := c.JSON(http.StatusCreated, map[string]string{
			"message": "User created",
			"name":    req.Name,
		}); err != nil {
			log.Printf("Failed to write response: %v", err)
		}
	})

	fmt.Println("Handler with MustBind registered")
}
Output:
Handler with MustBind registered

Types

type AccessLogScope added in v0.18.0

type AccessLogScope string

AccessLogScope controls which HTTP requests are logged as access logs. When unset, production defaults to AccessLogScopeErrorsOnly and development to AccessLogScopeAll.

const (
	// AccessLogScopeAll logs every request (including 2xx). Use in production only if you need full request logs.
	AccessLogScopeAll AccessLogScope = "all"
	// AccessLogScopeErrorsOnly logs only errors (status >= 400) and slow requests. Reduces log volume.
	AccessLogScopeErrorsOnly AccessLogScope = "errors_only"
)

type App

type App struct {
	// contains filtered or unexported fields
}

App represents the high-level application framework. App wraps the router with integrated observability and common middleware. Create an App using New or MustNew.

func MustNew

func MustNew(opts ...Option) *App

MustNew creates a new App instance or panics on error. It is a convenience function that panics if initialization fails, useful for initialization in main() functions.

Example:

app := app.MustNew(
    app.WithServiceName("my-service"),
    app.WithServiceVersion("v1.0.0"),
    app.WithObservability(
        app.WithLogging(logging.WithJSONHandler()),
        app.WithMetrics(), // Prometheus is default
        app.WithTracing(tracing.WithOTLP("localhost:4317")),
    ),
)

func New

func New(opts ...Option) (*App, error)

New creates a new App instance with the given options. New returns an error if the configuration is invalid or initialization fails.

Example:

app, err := app.New(
    app.WithServiceName("my-service"),
    app.WithObservability(
        app.WithMetrics(), // Prometheus is default
        app.WithTracing(tracing.WithOTLP("localhost:4317")),
    ),
)
if err != nil {
    log.Fatal(err)
}

func (*App) Any

func (a *App) Any(path string, handler HandlerFunc, opts ...RouteOption) *route.Route

Any registers a route that matches all HTTP methods. It is useful for catch-all endpoints like health checks or proxies.

It registers 7 separate routes internally (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS). For endpoints that only need specific methods, use individual method registrations (GET, POST, etc.).

Returns the GET route (most common for docs/constraints).

Example:

app.Any("/health", healthCheckHandler)
app.Any("/webhook/*", webhookProxyHandler)

func (*App) BaseLogger

func (a *App) BaseLogger() *slog.Logger

BaseLogger returns the application's base logger without request-specific context. BaseLogger should be used for background jobs, startup/shutdown logging, or other non-request contexts.

BaseLogger never returns nil - if no logger is configured, a no-op logger is returned.

Example:

app := app.New(...)
app.BaseLogger().Info("application started",
    slog.String("port", "8080"),
    slog.String("environment", "production"),
)

// Background job
go func() {
    app.BaseLogger().Info("background job started")
    // ... do work
}()

func (*App) DELETE

func (a *App) DELETE(path string, handler HandlerFunc, opts ...RouteOption) *route.Route

DELETE registers a DELETE route with optional middleware and OpenAPI documentation.

Example:

app.DELETE("/users/:id", deleteUser,
    app.WithBefore(authMiddleware),
    app.WithDoc(
        openapi.WithSummary("Delete user"),
        openapi.WithResponse(204, nil),
    ),
)

func (*App) Environment

func (a *App) Environment() string

Environment returns the current environment (development or production).

Example:

if app.Environment() == "production" {
    // Enable production-only features
}

func (*App) File

func (a *App) File(path, filepath string)

File serves a single file at the given path. File is commonly used for serving favicon.ico, robots.txt, etc.

Example:

app.File("/favicon.ico", "./static/favicon.ico")
app.File("/robots.txt", "./static/robots.txt")

func (*App) GET

func (a *App) GET(path string, handler HandlerFunc, opts ...RouteOption) *route.Route

GET registers a GET route with optional middleware and OpenAPI documentation.

The first parameter is the main handler. Additional options can configure: - Pre-handler middleware (WithBefore) - Post-handler middleware (WithAfter) - OpenAPI documentation (WithDoc)

Example:

app.GET("/users/:id", getUser,
    app.WithBefore(authMiddleware, rateLimitMiddleware),
    app.WithAfter(auditLogMiddleware),
    app.WithDoc(
        openapi.WithSummary("Get user"),
        openapi.WithDescription("Retrieves a user by ID"),
        openapi.WithResponse(200, UserResponse{}),
        openapi.WithResponse(404, ErrorResponse{}),
        openapi.WithTags("users"),
    ),
)

Returns the underlying router.Route for setting constraints:

app.GET("/users/:id", getUser, opts...).WhereInt("id")

func (*App) GetMetricsHandler

func (a *App) GetMetricsHandler() (http.Handler, error)

GetMetricsHandler returns the metrics HTTP handler if metrics are enabled. It returns an error if metrics are not enabled or if using a non-Prometheus provider.

Example:

handler, err := app.GetMetricsHandler()
if err != nil {
    return err
}
http.Handle("/metrics", handler)

func (*App) GetMetricsServerAddress

func (a *App) GetMetricsServerAddress() string

GetMetricsServerAddress returns the metrics server address if metrics are enabled. It returns an empty string if metrics are not enabled.

Example:

addr := app.GetMetricsServerAddress()
if addr != "" {
    fmt.Printf("Metrics available at http://%s/metrics\n", addr)
}

func (*App) Group

func (a *App) Group(prefix string, middleware ...HandlerFunc) *Group

Group creates a new route group. Group creates groups that support HandlerFunc (with Context), providing access to binding and validation features.

Example:

api := app.Group("/api/v1", AuthMiddleware())
api.GET("/users", handler)    // handler receives *app.Context
api.POST("/users", handler)    // handler receives *app.Context

func (*App) HEAD

func (a *App) HEAD(path string, handler HandlerFunc, opts ...RouteOption) *route.Route

HEAD registers a HEAD route with optional middleware and OpenAPI documentation.

Example:

app.HEAD("/users/:id", handler).WhereInt("id")

func (*App) Metrics

func (a *App) Metrics() *metrics.Recorder

Metrics returns the metrics configuration if enabled. It returns nil if metrics are not enabled.

Example:

if recorder := app.Metrics(); recorder != nil {
    // Access metrics recorder
}

func (*App) MustURLFor

func (a *App) MustURLFor(routeName string, params map[string]string, query map[string][]string) string

MustURLFor generates a URL from a route name and parameters, panicking on error. MustURLFor should be used when you're certain the route exists and all parameters are provided.

Example:

url := app.MustURLFor("users.get", map[string]string{"id": "123"}, nil)
// Returns: "/users/123"

func (*App) NoRoute

func (a *App) NoRoute(handler HandlerFunc)

NoRoute sets the handler for requests that don't match any registered routes. NoRoute allows customizing 404 error responses instead of using the default http.NotFound.

Example:

app.NoRoute(func(c *Context) {
    c.JSON(http.StatusNotFound, map[string]string{"error": "route not found"})
})

Setting handler to nil restores the default http.NotFound behavior.

func (*App) OPTIONS

func (a *App) OPTIONS(path string, handler HandlerFunc, opts ...RouteOption) *route.Route

OPTIONS registers an OPTIONS route with optional middleware and OpenAPI documentation.

Example:

app.OPTIONS("/users", handler)

func (*App) OnReady

func (a *App) OnReady(fn func()) error

OnReady registers a hook that runs after the server starts listening. Hooks can run asynchronously and errors are logged but don't stop the server. It should be used for warmup tasks, service discovery registration, etc.

Returns ErrRouterFrozen if called after the router is frozen (e.g. after Start() or Freeze()). Register all hooks before starting the server.

Example:

app.OnReady(func() {
    log.Println("Server ready!")
    registerWithConsul()
})

func (*App) OnReload added in v0.14.0

func (a *App) OnReload(fn func(context.Context) error) error

OnReload registers a hook that runs when the application receives a reload signal (SIGHUP) or when Reload() is called programmatically. Hooks run sequentially, and if any hook returns an error, subsequent hooks are skipped. Reload errors are logged but do not stop the server - it continues serving with the old configuration.

OnReload should be used for reloading runtime configuration without restarting the server:

  • Re-reading configuration files
  • Rotating TLS certificates
  • Flushing caches
  • Adjusting log levels
  • Updating connection pool settings

SIGHUP signal handling is automatically enabled when at least one OnReload hook is registered. On Unix systems, sending SIGHUP to the process will trigger all registered reload hooks. On Windows, SIGHUP is not available, but Reload() can still be called programmatically.

Note: Routes and middleware cannot be reloaded as the router is frozen after startup.

Returns ErrRouterFrozen if called after the router is frozen (e.g. after Start() or Freeze()). Register all hooks before starting the server.

Example:

app.OnReload(func(ctx context.Context) error {
    cfg, err := loadConfig("config.yaml")
    if err != nil {
        return fmt.Errorf("failed to load config: %w", err)
    }
    applyConfig(cfg)
    return nil
})
Example

ExampleApp_OnReload demonstrates how to use the OnReload hook to reload configuration when SIGHUP is received.

package main

import (
	"context"
	"fmt"
	"log"
	"os"
	"os/signal"
	"syscall"

	"rivaas.dev/app"
)

func main() {
	// Create app - SIGHUP handling is automatically enabled when hooks are registered
	myApp := app.MustNew(
		app.WithServiceName("example-reload"),
	)

	// Register a reload hook to reload configuration
	// SIGHUP is automatically enabled when this hook is registered
	if err := myApp.OnReload(func(ctx context.Context) error {
		fmt.Println("Reloading configuration...")
		// In a real application, you would:
		// - Re-read config files
		// - Rotate TLS certificates
		// - Flush caches
		// - Update connection pool settings
		return nil
	}); err != nil {
		log.Fatal(err)
	}

	myApp.GET("/", func(c *app.Context) {
		_ = c.String(200, "Hello, World!") //nolint:errcheck // Example code
	})

	// Set up signal handling for graceful shutdown
	ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
	defer cancel()

	// Start server - SIGHUP will trigger reload hooks automatically
	if err := myApp.Start(ctx); err != nil {
		log.Fatal(err)
	}
}

func (*App) OnRoute

func (a *App) OnRoute(fn func(*route.Route)) error

OnRoute registers a hook that fires when a route is registered. OnRoute is useful for route validation, logging, or documentation generation. OnRoute hooks are disabled after router is frozen.

Returns ErrRouterFrozen if called after the router is frozen (e.g. after Start() or Freeze()). Register all hooks before starting the server.

Example:

app.OnRoute(func(rt *route.Route) {
    log.Printf("Registered: %s %s", rt.Method(), rt.Path())
})

func (*App) OnShutdown

func (a *App) OnShutdown(fn func(context.Context)) error

OnShutdown registers a hook that runs during graceful shutdown. Hooks run in reverse order (LIFO) and receive a context with the shutdown timeout. It should be used for cleanup that must complete within the timeout (closing connections, flushing buffers).

Returns ErrRouterFrozen if called after the router is frozen (e.g. after Start() or Freeze()). Register all hooks before starting the server.

Example:

app.OnShutdown(func(ctx context.Context) {
    db.Close()
    flushMetrics(ctx)
})

func (*App) OnStart

func (a *App) OnStart(fn func(context.Context) error) error

OnStart registers a hook that runs before the server starts listening. Hooks run sequentially, and if any hook returns an error, startup is aborted. It should be used for initialization that must succeed (database connections, migrations, etc.).

Returns ErrRouterFrozen if called after the router is frozen (e.g. after Start() or Freeze()). Register all hooks before starting the server.

Example:

app.OnStart(func(ctx context.Context) error {
    return db.PingContext(ctx)
})

func (*App) OnStop

func (a *App) OnStop(fn func()) error

OnStop registers a hook that runs after the server stops. Hooks run in best-effort mode - panics are caught and logged. It should be used for final cleanup that doesn't need to complete within a timeout.

Returns ErrRouterFrozen if called after the router is frozen (e.g. after Start() or Freeze()). Register all hooks before starting the server.

Example:

app.OnStop(func() {
    cleanupTempFiles()
})

func (*App) PATCH

func (a *App) PATCH(path string, handler HandlerFunc, opts ...RouteOption) *route.Route

PATCH registers a PATCH route with optional middleware and OpenAPI documentation.

Example:

app.PATCH("/users/:id", patchUser,
    app.WithBefore(authMiddleware),
    app.WithDoc(
        openapi.WithSummary("Partially update user"),
        openapi.WithRequest(PatchUserRequest{}),
        openapi.WithResponse(200, UserResponse{}),
    ),
)

func (*App) POST

func (a *App) POST(path string, handler HandlerFunc, opts ...RouteOption) *route.Route

POST registers a POST route with optional middleware and OpenAPI documentation.

Example:

app.POST("/users", createUser,
    app.WithBefore(authMiddleware),
    app.WithDoc(
        openapi.WithSummary("Create user"),
        openapi.WithRequest(CreateUserRequest{}),
        openapi.WithResponse(201, UserResponse{}),
    ),
)

func (*App) PUT

func (a *App) PUT(path string, handler HandlerFunc, opts ...RouteOption) *route.Route

PUT registers a PUT route with optional middleware and OpenAPI documentation.

Example:

app.PUT("/users/:id", updateUser,
    app.WithBefore(authMiddleware),
    app.WithDoc(
        openapi.WithSummary("Update user"),
        openapi.WithRequest(UpdateUserRequest{}),
        openapi.WithResponse(200, UserResponse{}),
    ),
)

func (*App) PrintRoutes

func (a *App) PrintRoutes()

PrintRoutes prints all registered routes to stdout in a formatted table. It is useful for development and debugging to see all available routes.

It uses lipgloss/table for terminal output with color-coded HTTP methods and proper table formatting. A colorprofile.Writer automatically downsamples ANSI colors to match the terminal's capabilities (TrueColor → ANSI256 → ANSI). If output is not a TTY, ANSI sequences are stripped entirely. This respects the NO_COLOR environment variable and handles all terminal capability detection automatically.

Colors are only enabled in development mode.

Example output:

┌────────┬─────────┬──────────────────┬──────────────────┐
│ Method │ Version │ Path             │ Handler          │
├────────┼─────────┼──────────────────┼──────────────────┤
│ GET    │ -       │ /                │ handler          │
│ GET    │ v1      │ /users/:id       │ handler          │
│ POST   │ -       │ /users           │ handler          │
└────────┴─────────┴──────────────────┴──────────────────┘

func (*App) Readiness

func (a *App) Readiness() *ReadinessManager

Readiness returns the readiness manager for registering gates.

Example:

type DatabaseGate struct {
    db *sql.DB
}
func (g *DatabaseGate) Ready() bool { return g.db.Ping() == nil }
func (g *DatabaseGate) Name() string { return "database" }

app.Readiness().Register("db", &DatabaseGate{db: db})

func (*App) Reload added in v0.14.0

func (a *App) Reload(ctx context.Context) error

Reload triggers a reload of the application by executing all registered OnReload hooks. Reload can be called programmatically or is triggered automatically when SIGHUP is received (SIGHUP handling is automatically enabled when OnReload hooks are registered).

Hooks are executed sequentially, and if any hook returns an error, subsequent hooks are skipped. Reload errors are logged but do not stop the server - it continues serving with the old configuration.

Concurrent calls to Reload() are serialized via an internal mutex to prevent race conditions.

Example - Programmatic reload:

if err := app.Reload(ctx); err != nil {
    log.Printf("reload failed: %v", err)
}

Example - Admin endpoint:

app.POST("/admin/reload", func(c *app.Context) {
    if err := app.Reload(c.Request.Context()); err != nil {
        c.InternalError(err)
        return
    }
    c.JSON(200, map[string]string{"status": "reloaded"})
})
Example

ExampleApp_Reload demonstrates programmatic reload without SIGHUP.

package main

import (
	"context"
	"fmt"
	"log"

	"rivaas.dev/app"
)

func main() {
	myApp := app.MustNew(
		app.WithServiceName("example-reload-programmatic"),
	)

	var configVersion int

	if err := myApp.OnReload(func(ctx context.Context) error {
		configVersion++
		fmt.Printf("Config reloaded, version: %d\n", configVersion)
		return nil
	}); err != nil {
		log.Fatal(err)
	}

	// Create an admin endpoint that triggers reload
	myApp.POST("/admin/reload", func(c *app.Context) {
		if err := myApp.Reload(c.Request.Context()); err != nil {
			c.InternalError(err)
			return
		}
		_ = c.JSON(200, map[string]string{ //nolint:errcheck // Example code
			"status": "reloaded",
		})
	})

	// Programmatically trigger reload
	ctx := context.Background()
	if err := myApp.Reload(ctx); err != nil {
		log.Printf("reload failed: %v", err)
	}
}
Output:
Config reloaded, version: 1

func (*App) Route

func (a *App) Route(name string) (*route.Route, bool)

Route retrieves a route by name. It returns the route and true if found, false otherwise. It panics if the router is not frozen (call after app.Start() or app.Router().Freeze()).

Example:

route, ok := app.Route("users.get")
if ok {
    fmt.Printf("Route: %s %s\n", route.Method(), route.Path())
}

func (*App) Router

func (a *App) Router() *router.Router

Router returns the underlying router for advanced usage. It provides access to router-level features that are not exposed through App.

Example:

app.Router().Freeze() // Manually freeze router
app.Router().SetObservabilityRecorder(customRecorder)

func (*App) Routes

func (a *App) Routes() []*route.Route

Routes returns an immutable snapshot of all named routes. Routes panics if the router is not frozen (call after app.Start() or app.Router().Freeze()).

Example:

routes := app.Routes()
for _, route := range routes {
    fmt.Printf("%s: %s %s\n", route.Name(), route.Method(), route.Path())
}

func (*App) ServiceName

func (a *App) ServiceName() string

ServiceName returns the configured service name.

Example:

name := app.ServiceName()
fmt.Printf("Service: %s\n", name)

func (*App) ServiceVersion

func (a *App) ServiceVersion() string

ServiceVersion returns the configured service version.

Example:

version := app.ServiceVersion()
fmt.Printf("Version: %s\n", version)

func (*App) Start added in v0.3.0

func (a *App) Start(ctx context.Context) error

Start starts the server with graceful shutdown. Start automatically freezes the router before starting, making routes immutable. The server runs HTTP, HTTPS, or mTLS depending on configuration: use WithTLS or WithMTLS at construction to serve over TLS; otherwise plain HTTP is used.

The server listens on the address configured via WithPort and WithHost. Default is :8080 for HTTP and :8443 when using WithTLS or WithMTLS, overridable by WithPort and by RIVAAS_PORT and RIVAAS_HOST when WithEnv is used.

The context controls the application lifecycle - when canceled, it triggers graceful shutdown of the server and all observability components (metrics, tracing).

Note: Signal handling should be configured by the caller using signal.NotifyContext. This follows the Go pattern of explicit signal handling at the application boundary.

Example:

ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer cancel()

app := app.MustNew(
    app.WithServiceName("my-service"),
    app.WithPort(3000),
)
if err := app.Start(ctx); err != nil {
    log.Fatal(err)
}

HTTPS example (default port 8443):

app := app.MustNew(
    app.WithServiceName("my-service"),
    app.WithTLS("server.crt", "server.key"),
)
if err := app.Start(ctx); err != nil { ... }

func (*App) Static

func (a *App) Static(prefix, root string)

Static serves static files from the given directory. Static is a convenience wrapper that delegates to router.Static.

Example:

app.Static("/static", "./public")

func (*App) StaticFS

func (a *App) StaticFS(prefix string, fs http.FileSystem)

StaticFS serves files from the given filesystem. StaticFS is particularly useful with Go's embed.FS for embedding static assets.

Example:

//go:embed static
var staticFiles embed.FS
app.StaticFS("/static", http.FS(staticFiles))

func (*App) Test

func (a *App) Test(req *http.Request, opts ...TestOption) (*http.Response, error)

Test executes an HTTP request against the app without starting a server. Test is useful for unit testing handlers and middleware.

The request is executed in a goroutine with an optional timeout via context. If a timeout occurs, Test returns an error immediately, but the handler goroutine may continue running until it completes (the router's ServeHTTP cannot be canceled mid-execution). This is acceptable for test scenarios where handlers are expected to complete within a reasonable time.

Test returns an *http.Response that can be inspected for status, headers, and body.

Example:

req := httptest.NewRequest("GET", "/users/123", nil)
resp, err := app.Test(req)
if err != nil {
    t.Fatal(err)
}
assert.Equal(t, 200, resp.StatusCode)

func (*App) TestJSON

func (a *App) TestJSON(method, path string, body any, opts ...TestOption) (*http.Response, error)

TestJSON is a convenience method for testing JSON requests. TestJSON automatically sets Content-Type and encodes the body as JSON.

Example:

body := map[string]string{"name": "Alice"}
resp, err := app.TestJSON("POST", "/users", body)

func (*App) Tracing

func (a *App) Tracing() *tracing.Tracer

Tracing returns the tracing configuration if enabled. It returns nil if tracing is not enabled.

Example:

if cfg := app.Tracing(); cfg != nil {
    // Access tracing configuration
}

func (*App) URLFor

func (a *App) URLFor(routeName string, params map[string]string, query map[string][]string) (string, error)

URLFor generates a URL from a route name and parameters. URLFor returns an error if the route is not found or if required parameters are missing.

Example:

url, err := app.URLFor("users.get", map[string]string{"id": "123"}, nil)
// Returns: "/users/123", nil

func (*App) Use

func (a *App) Use(middleware ...HandlerFunc)

Use adds middleware to the app. Use adds middleware that will be executed for all routes.

Example:

app.Use(AuthMiddleware(), LoggingMiddleware())
app.GET("/users", handler) // Will execute auth + logging + handler

func (*App) ValidateRoutes added in v0.20.0

func (a *App) ValidateRoutes() error

ValidateRoutes returns all route-option validation errors (e.g. nil route options) collected during route registration. Call before starting the server (e.g. before [Router].Freeze or before passing the app to a runner) so config errors are reported at init. Returns nil if there are no route validation errors.

func (*App) Version

func (a *App) Version(version string) *VersionGroup

Version creates a version group that supports HandlerFunc. Version allows using Context features (binding, validation, logging) with router versioning.

Routes registered in a version group are automatically scoped to that version. The version is detected from the request path, headers, query parameters, or other configured versioning strategies.

Example:

v1 := app.Version("v1")
v1.GET("/status", handlers.Status)
v1.POST("/users", handlers.CreateUser)

func (*App) WrapHandler

func (a *App) WrapHandler(handler HandlerFunc) router.HandlerFunc

WrapHandler wraps an app.HandlerFunc to convert it to a router.HandlerFunc. It creates an app.Context from the router.Context and manages pooling.

The context is guaranteed to be returned to the pool even if the handler panics, ensuring no context leaks occur. The recovery middleware will still catch panics for proper error handling, but this ensures resource cleanup.

It is useful when you need to use router-level features (like route constraints) while still using app.HandlerFunc with full app.Context support.

Example:

a.Router().GET("/users/:id", a.WrapHandler(handlers.GetUserByID)).WhereInt("id")

type BindOption added in v0.9.0

type BindOption func(*bindConfig)

BindOption configures Bind behavior. BindOptions can be passed to Context.Bind, Context.MustBind, Bind, and MustBind.

func WithBindingOptions added in v0.9.0

func WithBindingOptions(opts ...binding.Option) BindOption

WithBindingOptions passes options directly to the binding package. Use for advanced binding configuration.

Example:

req, err := app.Bind[Request](c,
    app.WithBindingOptions(
        binding.WithTimeLayouts("2006-01-02"),
        binding.WithMaxDepth(16),
    ),
)

func WithPartial added in v0.9.0

func WithPartial() BindOption

WithPartial enables partial validation for PATCH requests. Only fields present in the request body are validated. The "required" constraint is ignored for absent fields.

Example:

req, err := app.Bind[UpdateUserRequest](c, app.WithPartial())

func WithPresence added in v0.9.0

func WithPresence(pm validation.PresenceMap) BindOption

WithPresence explicitly sets the presence map. Usually auto-detected from JSON body; use this for custom scenarios.

Example:

pm, _ := validation.ComputePresence(rawJSON)
req, err := app.Bind[Request](c, app.WithPresence(pm))

func WithStrict added in v0.9.0

func WithStrict() BindOption

WithStrict rejects unknown JSON fields during binding. Use this to catch typos and API drift early.

Example:

req, err := app.Bind[CreateUserRequest](c, app.WithStrict())

Equivalent to the old BindAndValidateStrict method.

func WithValidationOptions added in v0.9.0

func WithValidationOptions(opts ...validation.Option) BindOption

WithValidationOptions passes options directly to the validation package. Use for advanced validation configuration.

Example:

req, err := app.Bind[Request](c,
    app.WithValidationOptions(
        validation.WithStrategy(validation.StrategyTags),
        validation.WithMaxErrors(10),
    ),
)

func WithoutValidation added in v0.9.0

func WithoutValidation() BindOption

WithoutValidation skips the validation step. Use when you only need binding, or will validate separately.

Example:

req, err := app.Bind[Request](c, app.WithoutValidation())

Equivalent to using Context.BindOnly or BindOnly.

type CheckFunc

type CheckFunc func(ctx context.Context) error

CheckFunc defines a function that performs a health or readiness check. The function should return nil if the check passes, or an error if it fails. The context may be canceled if the check takes too long.

type ConfigError

type ConfigError struct {
	// Field is the name of the configuration field that failed validation
	Field string
	// Value is the actual value that was provided (may be nil for missing values)
	Value any
	// Message is a human-readable error message explaining the validation failure
	Message string
	// Constraint is an optional constraint that was violated (e.g., "must be positive", "must be between X and Y")
	Constraint string
	// Hint is an optional suggestion for how to fix the error (e.g., which option or env var to use).
	// When non-empty, Error() appends it to the returned string.
	Hint string
}

ConfigError represents a configuration validation error with structured information.

ConfigError provides structured error information that enables:

  • Programmatic error inspection (field-level error detection)
  • Rich formatting for CLI/web UI display
  • Error aggregation for batch validation
  • Internationalization support (field names remain constant)

Validation happens once at startup, not during request handling.

func (*ConfigError) Error

func (e *ConfigError) Error() string

Error implements the error interface and returns a formatted error message. It formats the ConfigError as a human-readable string. When Hint is non-empty, it is appended in parentheses so the message is actionable.

func (*ConfigError) Unwrap

func (e *ConfigError) Unwrap() error

Unwrap returns nil as ConfigError is a leaf error type. It allows errors.Is() and errors.As() to work correctly.

type ConfigErrors added in v0.20.0

type ConfigErrors struct {
	Errors []*ConfigError
}

ConfigErrors represents multiple configuration validation errors. ConfigErrors allows collecting all config errors before returning them.

func (*ConfigErrors) Add added in v0.20.0

func (ce *ConfigErrors) Add(err *ConfigError)

Add appends a new ConfigError to the ConfigErrors. It collects config errors for batch reporting.

func (*ConfigErrors) Error added in v0.20.0

func (ce *ConfigErrors) Error() string

Error implements the error interface and returns a formatted error message listing all config errors.

func (*ConfigErrors) HasErrors added in v0.20.0

func (ce *ConfigErrors) HasErrors() bool

HasErrors returns true if there are any config errors. It checks if the ConfigErrors contains any errors.

func (*ConfigErrors) ToError added in v0.20.0

func (ce *ConfigErrors) ToError() error

ToError returns nil if there are no errors, otherwise returns the ConfigErrors as an error. It is useful for returning from config validation functions.

type Context

type Context struct {
	*router.Context // Embed router context for HTTP functionality
	// contains filtered or unexported fields
}

Context wraps router.Context with app-level features including binding and validation. Context embeds router.Context to provide all HTTP routing functionality while adding high-level integration with binding and validation packages.

Context instances are pooled by the App and reused across requests. They are normally created and initialized by the App (via the pool and wrapHandler). When constructing a Context for tests (e.g. from the pool), set app for app-specific error formatter, logging, and observability. If app is nil, methods like Fail() still work using the default error formatter and slog.Default().

func TestContextWithBody added in v0.9.0

func TestContextWithBody(method, path string, body any) (*Context, error)

TestContextWithBody creates a Context with JSON body for testing. TestContextWithBody is useful for testing binding and validation logic.

Example:

body := map[string]string{"name": "Alice", "email": "alice@example.com"}
c, err := app.TestContextWithBody("POST", "/users", body)
if err != nil {
    t.Fatal(err)
}
var req CreateUserRequest
err = c.Bind(&req)

func TestContextWithBodyAndApp added in v0.20.0

func TestContextWithBodyAndApp(a *App, method, path string, body any) (*Context, error)

TestContextWithBodyAndApp creates a Context with JSON body using the given App. Use this when testing app-specific configuration (e.g. WithValidationEngine).

func TestContextWithForm added in v0.9.0

func TestContextWithForm(method, path string, values map[string][]string) (*Context, error)

TestContextWithForm creates a Context with form data for testing. TestContextWithForm is useful for testing form binding logic.

Example:

values := map[string][]string{
    "name":  {"Alice"},
    "email": {"alice@example.com"},
}
c, err := app.TestContextWithForm("POST", "/users", values)
if err != nil {
    t.Fatal(err)
}
var req CreateUserRequest
err = c.Bind(&req)

func (*Context) AddCounter added in v0.19.0

func (c *Context) AddCounter(name string, value int64, attributes ...attribute.KeyValue)

AddCounter adds a value to a custom counter metric. This is a no-op when metrics are not configured.

Example

ExampleContext_AddCounter demonstrates adding to a counter by an arbitrary amount.

package main

import (
	"fmt"
	"log"
	"net/http"
	"net/http/httptest"

	"go.opentelemetry.io/otel/attribute"

	"rivaas.dev/app"
	"rivaas.dev/metrics"
)

func main() {
	a := app.MustNew(
		app.WithObservability(app.WithMetrics(metrics.WithStdout())),
	)

	a.POST("/upload", func(c *app.Context) {
		// Simulate processing N bytes
		bytesProcessed := int64(1024)
		c.AddCounter("bytes_processed", bytesProcessed, attribute.String("type", "upload"))
		c.IncrementCounter("uploads_total")
		if err := c.JSON(http.StatusOK, map[string]string{"status": "ok"}); err != nil {
			log.Printf("Failed to write response: %v", err)
		}
	})

	req := httptest.NewRequest(http.MethodPost, "/upload", nil)
	resp, err := a.Test(req)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Status: %d\n", resp.StatusCode)
	//nolint:errcheck // example cleanup
	resp.Body.Close()
}
Output:
Status: 200

func (*Context) AddSpanEvent added in v0.17.0

func (c *Context) AddSpanEvent(name string, attrs ...attribute.KeyValue)

AddSpanEvent adds an event to the current span with optional attributes. This is a no-op if tracing is not active.

func (*Context) BadRequest

func (c *Context) BadRequest(err error)

BadRequest responds with a 400 Bad Request error. Pass nil for a generic "Bad Request" message.

Example:

c.BadRequest(nil)              // generic
c.BadRequest(validationErr)    // with validation details

func (*Context) Bind

func (c *Context) Bind(out any, opts ...BindOption) error

Bind binds request data and validates it. Bind is the recommended method for handling request input.

Bind automatically:

  • Detects Content-Type and binds from appropriate sources
  • Binds path, query, header, and cookie parameters based on struct tags
  • Validates the bound struct using the configured strategy
  • Tracks field presence for partial validation support

Supported sources based on tags:

  • path: "name" - URL path parameters
  • query: "name" - Query string parameters
  • header: "name" - HTTP headers
  • cookie: "name" - Cookies
  • json: "name" - JSON request body
  • form: "name" - Form data (application/x-www-form-urlencoded or multipart/form-data)

For binding without validation, use Context.BindOnly. For separate binding and validation, use Context.BindOnly and Context.Validate.

Errors:

Example:

var req CreateUserRequest
if err := c.Bind(&req); err != nil {
    c.Fail(err)
    return
}

With options:

if err := c.Bind(&req, app.WithStrict(), app.WithPartial()); err != nil {
    c.Fail(err)
    return
}

Note: For multipart forms with file uploads, files must be retrieved separately using c.File() or c.Files().

Example

ExampleContext_Bind demonstrates basic request binding and validation.

package main

import (
	"fmt"
	"log"
	"net/http"

	"rivaas.dev/app"
)

func main() {
	a := app.MustNew()

	type CreateUserRequest struct {
		Name  string `json:"name" validate:"required,min=3"`
		Email string `json:"email" validate:"required,email"`
	}

	a.POST("/users", func(c *app.Context) {
		var req CreateUserRequest
		if err := c.Bind(&req); err != nil {
			c.Fail(err)
			return
		}

		if err := c.JSON(http.StatusCreated, map[string]string{
			"message": "User created",
			"name":    req.Name,
		}); err != nil {
			log.Printf("Failed to write response: %v", err)
		}
	})

	fmt.Println("Handler with binding and validation registered")
}
Output:
Handler with binding and validation registered
Example (WithOptions)

ExampleContext_Bind_withOptions demonstrates binding with options.

package main

import (
	"fmt"
	"log"
	"net/http"

	"rivaas.dev/app"
)

func main() {
	a := app.MustNew()

	type CreateUserRequest struct {
		Name  string `json:"name" validate:"required,min=3"`
		Email string `json:"email" validate:"required,email"`
	}

	a.POST("/users", func(c *app.Context) {
		var req CreateUserRequest
		if err := c.Bind(&req, app.WithStrict()); err != nil {
			c.Fail(err)
			return
		}

		if err := c.JSON(http.StatusCreated, map[string]string{
			"message": "User created",
		}); err != nil {
			log.Printf("Failed to write response: %v", err)
		}
	})

	fmt.Println("Handler with strict binding registered")
}
Output:
Handler with strict binding registered

func (*Context) BindOnly added in v0.9.0

func (c *Context) BindOnly(out any, opts ...BindOption) error

BindOnly binds request data without validation. Use when you need fine-grained control over the bind/validate lifecycle.

Example:

var req Request
if err := c.BindOnly(&req); err != nil {
    c.Fail(err)
    return
}
req.Normalize() // Custom processing
if err := c.Validate(&req); err != nil {
    c.Fail(err)
    return
}
Example

ExampleContext_BindOnly demonstrates binding without validation.

package main

import (
	"fmt"
	"log"
	"net/http"

	"rivaas.dev/app"
)

func main() {
	a := app.MustNew()

	type CreateUserRequest struct {
		Name  string `json:"name"`
		Email string `json:"email"`
	}

	a.POST("/users", func(c *app.Context) {
		var req CreateUserRequest
		if err := c.BindOnly(&req); err != nil {
			c.Fail(err)
			return
		}

		// Custom processing before validation
		req.Email = normalizeEmail(req.Email)

		// Validate separately
		if err := c.Validate(&req); err != nil {
			c.Fail(err)
			return
		}

		if err := c.JSON(http.StatusCreated, map[string]string{
			"message": "User created",
		}); err != nil {
			log.Printf("Failed to write response: %v", err)
		}
	})

	fmt.Println("Handler with separate bind and validate registered")
}

func normalizeEmail(email string) string {

	return email
}
Output:
Handler with separate bind and validate registered

func (*Context) Conflict added in v0.11.0

func (c *Context) Conflict(err error)

Conflict responds with a 409 Conflict error. Pass nil for a generic "Conflict" message.

Example:

c.Conflict(nil)                                    // generic
c.Conflict(fmt.Errorf("user already exists"))     // custom message

func (*Context) CopyTraceContext added in v0.20.0

func (c *Context) CopyTraceContext() context.Context

CopyTraceContext returns a new context that carries the current trace for use in goroutines or background work. Delegates to tracing.CopyTraceContext(c.RequestContext()).

func (*Context) Fail added in v0.11.0

func (c *Context) Fail(err error)

Fail responds with a formatted error using the configured formatter. Fail automatically aborts the handler chain after writing the response.

Fail is the recommended way to return errors in handlers. The HTTP status is determined from the error (via ErrorType interface) or defaults to 500 Internal Server Error.

Fail selects the formatter based on:

  • Content negotiation (Accept header) if multiple formatters are configured
  • Default formatter if single formatter is configured
  • RFC 9457 formatter as ultimate fallback

Example:

if err := c.Bind(&req); err != nil {
    c.Fail(err)
    return
}

if user == nil {
    c.Fail(fmt.Errorf("user not found"))
    return
}

See also Context.FailStatus for explicit status codes and convenience methods like Context.NotFound, Context.BadRequest, Context.Unauthorized.

To collect multiple errors and respond later (e.g. multi-field validation), use c.Context.CollectError(err), then c.Context.HasErrors() / c.Context.Errors() and send one response.

func (*Context) FailStatus added in v0.11.0

func (c *Context) FailStatus(status int, err error)

FailStatus responds with an error and explicit status code. FailStatus automatically aborts the handler chain.

FailStatus is useful when you want to override the error's default status.

Example:

c.FailStatus(http.StatusNotFound, err)
c.FailStatus(http.StatusBadRequest, validationErr)

func (*Context) FinishSpan added in v0.19.0

func (c *Context) FinishSpan(span trace.Span)

FinishSpan ends a child span with status Ok. Use for spans that complete successfully and have no HTTP status. Delegates to tracing.FinishSpan. No-op if app/tracing nil.

Example

ExampleContext_FinishSpan demonstrates ending a child span with success.

package main

import (
	"fmt"
	"log"
	"net/http"
	"net/http/httptest"

	"rivaas.dev/app"
	"rivaas.dev/tracing"
)

func main() {
	a := app.MustNew(
		app.WithObservability(app.WithTracing(tracing.WithNoop())),
	)
	a.GET("/ping", func(c *app.Context) {
		_, span := c.StartSpan("check")
		defer c.FinishSpan(span)
		if err := c.JSON(http.StatusOK, map[string]string{"ok": "true"}); err != nil {
			log.Printf("Failed to write response: %v", err)
		}
	})
	req := httptest.NewRequest(http.MethodGet, "/ping", nil)
	resp, err := a.Test(req)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Status: %d\n", resp.StatusCode)
	//nolint:errcheck // example cleanup
	resp.Body.Close()
}
Output:
Status: 200

func (*Context) FinishSpanWithError added in v0.20.0

func (c *Context) FinishSpanWithError(span trace.Span, err error)

FinishSpanWithError marks the span as failed with the given error and ends it. Delegates to tracing.FinishSpanWithError. No-op if app/tracing nil.

Example

ExampleContext_FinishSpanWithError demonstrates ending a span with an error.

package main

import (
	"errors"
	"fmt"
	"log"
	"net/http"
	"net/http/httptest"

	"rivaas.dev/app"
	"rivaas.dev/tracing"
)

func main() {
	a := app.MustNew(
		app.WithObservability(app.WithTracing(tracing.WithNoop())),
	)
	a.GET("/fail", func(c *app.Context) {
		_, span := c.StartSpan("op")
		err := errors.New("something failed")
		c.FinishSpanWithError(span, err)
		if werr := c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()}); werr != nil {
			log.Printf("Failed to write response: %v", werr)
		}
	})
	req := httptest.NewRequest(http.MethodGet, "/fail", nil)
	resp, err := a.Test(req)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Status: %d\n", resp.StatusCode)
	//nolint:errcheck // example cleanup
	resp.Body.Close()
}
Output:
Status: 500

func (*Context) FinishSpanWithHTTPStatus added in v0.20.0

func (c *Context) FinishSpanWithHTTPStatus(span trace.Span, statusCode int)

FinishSpanWithHTTPStatus ends a child span and sets status from the HTTP status code. Delegates to tracing.FinishSpanWithHTTPStatus. No-op if app/tracing nil.

func (*Context) Forbidden

func (c *Context) Forbidden(err error)

Forbidden responds with a 403 Forbidden error. Pass nil for a generic "Forbidden" message.

Example:

c.Forbidden(nil)                                      // generic
c.Forbidden(fmt.Errorf("insufficient permissions"))  // custom message

func (*Context) Gone added in v0.11.0

func (c *Context) Gone(err error)

Gone responds with a 410 Gone error. Pass nil for a generic "Gone" message.

Example:

c.Gone(nil)                                        // generic
c.Gone(fmt.Errorf("resource permanently deleted")) // custom message

func (*Context) IncrementCounter added in v0.17.0

func (c *Context) IncrementCounter(name string, attributes ...attribute.KeyValue)

IncrementCounter increments a custom counter metric by one. This is a no-op when metrics are not configured.

func (*Context) InternalError

func (c *Context) InternalError(err error)

InternalError responds with a 500 Internal Server Error. Pass nil for a generic "Internal Server Error" message.

Example:

c.InternalError(nil)   // generic
c.InternalError(err)   // with error details (logged but sanitized in response)

func (*Context) MustBind added in v0.9.0

func (c *Context) MustBind(out any, opts ...BindOption) bool

MustBind binds and validates, writing an error response on failure. Returns true if successful, false if an error was written.

MustBind eliminates boilerplate error handling for the common case.

Example:

var req CreateUserRequest
if !c.MustBind(&req) {
    return // Error already written
}
// Continue with validated request
Example

ExampleContext_MustBind demonstrates the Must pattern for binding.

package main

import (
	"fmt"
	"log"
	"net/http"

	"rivaas.dev/app"
)

func main() {
	a := app.MustNew()

	type CreateUserRequest struct {
		Name  string `json:"name" validate:"required,min=3"`
		Email string `json:"email" validate:"required,email"`
	}

	a.POST("/users", func(c *app.Context) {
		var req CreateUserRequest
		if !c.MustBind(&req) {
			return // Error already written
		}

		if err := c.JSON(http.StatusCreated, map[string]string{
			"message": "User created",
			"name":    req.Name,
		}); err != nil {
			log.Printf("Failed to write response: %v", err)
		}
	})

	fmt.Println("Handler with MustBind registered")
}
Output:
Handler with MustBind registered

func (*Context) NotFound

func (c *Context) NotFound(err error)

NotFound responds with a 404 Not Found error. Pass nil for a generic "Not Found" message.

Example:

c.NotFound(nil)                                    // generic "Not Found"
c.NotFound(fmt.Errorf("user %s not found", id))   // custom message
c.NotFound(ErrUserNotFound)                       // domain error

func (*Context) Presence

func (c *Context) Presence() validation.PresenceMap

Presence returns the presence map for the current request. Presence returns nil if no binding has occurred yet.

Example:

pm := c.Presence()
if pm != nil && pm.Has("email") {
    // email field was present in request
}

func (*Context) RecordError added in v0.20.0

func (c *Context) RecordError(err error)

RecordError records an error on the current request span without ending it. Delegates to tracing.RecordErrorFromContext(c.RequestContext(), err). No-op if app/tracing nil.

func (*Context) RecordHistogram added in v0.17.0

func (c *Context) RecordHistogram(name string, value float64, attributes ...attribute.KeyValue)

RecordHistogram records a custom histogram metric. This is a no-op when metrics are not configured.

func (*Context) ResetBinding

func (c *Context) ResetBinding()

ResetBinding resets the binding metadata for this context. ResetBinding is useful for testing or when you need to rebind a request.

func (*Context) ServiceUnavailable added in v0.11.0

func (c *Context) ServiceUnavailable(err error)

ServiceUnavailable responds with a 503 Service Unavailable error. Pass nil for a generic "Service Unavailable" message.

Example:

c.ServiceUnavailable(nil)                              // generic
c.ServiceUnavailable(fmt.Errorf("maintenance mode"))  // custom message

func (*Context) SetGauge added in v0.17.0

func (c *Context) SetGauge(name string, value float64, attributes ...attribute.KeyValue)

SetGauge sets a custom gauge metric. This is a no-op when metrics are not configured.

func (*Context) SetSpanAttribute added in v0.17.0

func (c *Context) SetSpanAttribute(key string, value any)

SetSpanAttribute adds an attribute to the current span. This is a no-op if tracing is not active.

func (*Context) Span added in v0.17.0

func (c *Context) Span() trace.Span

Span returns the OpenTelemetry span for this request, if tracing is enabled. Returns a non-recording span if tracing is not enabled.

func (*Context) SpanID added in v0.17.0

func (c *Context) SpanID() string

SpanID returns the current span ID from the active span. Returns an empty string if tracing is not active.

func (*Context) StartSpan added in v0.19.0

func (c *Context) StartSpan(name string, opts ...trace.SpanStartOption) (context.Context, trace.Span)

StartSpan starts a child span with the given name. It is the single, discoverable way to create child spans from handlers. Always end the span with FinishSpan or FinishSpanWithHTTPStatus (e.g. defer c.FinishSpan(span)). If tracing is nil or disabled, returns the request context and a non-recording span.

Example

ExampleContext_StartSpan demonstrates creating a child span from a handler. Use StartSpan and defer FinishSpan for custom spans (e.g. db-query, external call).

package main

import (
	"fmt"
	"log"
	"net/http"
	"net/http/httptest"

	"go.opentelemetry.io/otel/attribute"

	"rivaas.dev/app"
	"rivaas.dev/tracing"
)

func main() {
	a := app.MustNew(
		app.WithObservability(app.WithTracing(tracing.WithNoop())),
	)

	a.GET("/users", func(c *app.Context) {
		ctx, span := c.StartSpan("db-query")
		defer c.FinishSpan(span)
		span.SetAttributes(attribute.String("db.operation", "list"))
		_ = ctx // use ctx for DB call so the span is the parent
		if err := c.JSON(http.StatusOK, map[string]string{"users": "list"}); err != nil {
			log.Printf("Failed to write response: %v", err)
		}
	})

	req := httptest.NewRequest(http.MethodGet, "/users", nil)
	resp, err := a.Test(req)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Status: %d\n", resp.StatusCode)
	//nolint:errcheck // example cleanup
	resp.Body.Close()
}
Output:
Status: 200

func (*Context) TooManyRequests added in v0.11.0

func (c *Context) TooManyRequests(err error)

TooManyRequests responds with a 429 Too Many Requests error. Pass nil for a generic "Too Many Requests" message.

Example:

c.TooManyRequests(nil)                                // generic
c.TooManyRequests(fmt.Errorf("rate limit exceeded")) // custom message

func (*Context) TraceContext added in v0.17.0

func (c *Context) TraceContext() context.Context

TraceContext returns the request context (which carries the active span when tracing is enabled). Use it for manual span creation or context propagation.

func (*Context) TraceID added in v0.17.0

func (c *Context) TraceID() string

TraceID returns the current trace ID from the active span. Returns an empty string if tracing is not active.

func (*Context) Tracer added in v0.19.0

func (c *Context) Tracer() *tracing.Tracer

Tracer returns the app's tracer, or nil if tracing is not configured. Use Tracer() only when you need to pass the tracer to another library (e.g. DB driver, HTTP client) or use tracer-specific options (inject/extract). For child spans in handlers, use StartSpan and FinishSpan instead.

func (*Context) Unauthorized

func (c *Context) Unauthorized(err error)

Unauthorized responds with a 401 Unauthorized error. Pass nil for a generic "Unauthorized" message.

Example:

c.Unauthorized(nil)                              // generic
c.Unauthorized(fmt.Errorf("invalid token"))     // custom message

func (*Context) UnprocessableEntity added in v0.11.0

func (c *Context) UnprocessableEntity(err error)

UnprocessableEntity responds with a 422 Unprocessable Entity error. Pass nil for a generic "Unprocessable Entity" message.

Example:

c.UnprocessableEntity(nil)           // generic
c.UnprocessableEntity(validationErr) // validation details

func (*Context) Validate added in v0.9.0

func (c *Context) Validate(v any, opts ...ValidateOption) error

Validate validates a struct using the configured validation strategy. Use after BindOnly for fine-grained control. Options are app-scoped: use WithValidatePartial, WithValidateStrict, or WithValidateOptions.

Example:

if err := c.Validate(&req, app.WithValidatePartial()); err != nil {
    c.Fail(err)
    return
}

func (*Context) WithSpan added in v0.20.0

func (c *Context) WithSpan(name string, fn func(context.Context) error) error

WithSpan runs fn under a new span with the given name. The span is finished with success if fn returns nil, or with error if fn returns non-nil. Returns the error from fn. No-op if app/tracing nil (runs fn and returns its error). Delegates to StartSpan and FinishSpan/FinishSpanWithError.

Example

ExampleContext_WithSpan demonstrates running a function under a span.

package main

import (
	"context"
	"fmt"
	"log"
	"net/http"
	"net/http/httptest"

	"rivaas.dev/app"
	"rivaas.dev/tracing"
)

func main() {
	a := app.MustNew(
		app.WithObservability(app.WithTracing(tracing.WithNoop())),
	)
	a.GET("/user/:id", func(c *app.Context) {
		id := c.Param("id")
		err := c.WithSpan("fetch-user", func(ctx context.Context) error {
			_ = ctx
			_ = id
			return nil
		})
		if err != nil {
			if werr := c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()}); werr != nil {
				log.Printf("Failed to write response: %v", werr)
			}
			return
		}
		if werr := c.JSON(http.StatusOK, map[string]string{"id": id}); werr != nil {
			log.Printf("Failed to write response: %v", werr)
		}
	})
	req := httptest.NewRequest(http.MethodGet, "/user/123", nil)
	resp, err := a.Test(req)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Status: %d\n", resp.StatusCode)
	//nolint:errcheck // example cleanup
	resp.Body.Close()
}
Output:
Status: 200

type DebugOption added in v0.2.0

type DebugOption func(*debugSettings)

DebugOption configures debug endpoint settings. These options configure pprof and other debug endpoints.

func WithDebugPrefix added in v0.2.0

func WithDebugPrefix(prefix string) DebugOption

WithDebugPrefix sets the mount prefix for debug endpoints. Default is "/debug", which mounts pprof at "/debug/pprof/*".

Example:

app.MustNew(
    app.WithDebugEndpoints(
        app.WithDebugPrefix("/_debug"),
        app.WithPprof(),
    ),
)
// Endpoints: /_debug/pprof/*, etc.

func WithPprof added in v0.2.0

func WithPprof() DebugOption

WithPprof enables pprof endpoints for profiling and debugging.

Security rationale: pprof endpoints are disabled by default and require explicit opt-in because they expose sensitive runtime information that can be exploited:

Attack vectors:

  • Goroutine dumps reveal internal logic and potential race conditions
  • Heap dumps may contain secrets, tokens, or PII in memory
  • CPU profiling can be used for timing attacks or DoS (profiling has overhead)
  • Alloc profiles reveal memory usage patterns useful for resource exhaustion attacks

Safe usage patterns:

  1. Development: Enable unconditionally (no external exposure)
  2. Staging: Enable behind VPN or IP allowlist
  3. Production: Enable only with proper authentication middleware Example: app.Use(authMiddleware); app.WithDebugEndpoints(app.WithPprof())

Endpoints registered (when enabled):

  • GET /debug/pprof/ - Main pprof index
  • GET /debug/pprof/cmdline - Command line
  • GET /debug/pprof/profile - CPU profile
  • GET /debug/pprof/symbol - Symbol lookup
  • POST /debug/pprof/symbol - Symbol lookup
  • GET /debug/pprof/trace - Execution trace
  • GET /debug/pprof/{profile} - Named profiles (allocs, block, goroutine, heap, mutex, threadcreate)

Example:

// Development: enable pprof
app.MustNew(
    app.WithDebugEndpoints(
        app.WithPprof(),
    ),
)

// Production: enable only if explicitly requested via environment
app.MustNew(
    app.WithDebugEndpoints(
        app.WithPprofIf(os.Getenv("PPROF_ENABLED") == "true"),
    ),
)

func WithPprofIf added in v0.2.0

func WithPprofIf(condition bool) DebugOption

WithPprofIf conditionally enables pprof endpoints based on the given condition. This is useful for environment-based configuration.

Example:

app.MustNew(
    app.WithDebugEndpoints(
        app.WithPprofIf(os.Getenv("PPROF_ENABLED") == "true"),
    ),
)

type Gate

type Gate interface {
	// Ready returns true if the component is ready to serve traffic.
	Ready() bool
	// Name returns the name of the gate for identification.
	Name() string
}

Gate represents a component that reports its readiness status. Used for runtime registration of readiness checks that need to be dynamically added or removed during application lifecycle.

For static readiness checks configured at startup, prefer using WithHealthEndpoints with WithReadinessCheck instead, as it provides better DX through the functional options pattern.

Use Gate when you need:

  • Dynamic registration/unregistration at runtime
  • Component-owned readiness state (e.g., database connection pool)
  • Integration with external libraries that manage their own state

type Group

type Group struct {
	// contains filtered or unexported fields
}

Group represents a route group that allows organizing related routes under a common path prefix with shared middleware. It enables hierarchical organization of API endpoints and middleware application.

Groups created from App support app.HandlerFunc (with app.Context), providing access to binding and validation features.

Example:

api := app.Group("/api/v1", AuthMiddleware())
api.GET("/users", handler)    // handler receives *app.Context
api.POST("/users", handler)   // handler receives *app.Context

func (*Group) Any

func (g *Group) Any(path string, handler HandlerFunc, opts ...RouteOption) *route.Route

Any registers a route that matches all HTTP methods. It is useful for catch-all endpoints like health checks or proxies.

It registers 7 separate routes internally (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS). For endpoints that only need specific methods, use individual method registrations (GET, POST, etc.).

Returns the GET route (most common for docs/constraints).

Example:

api := app.Group("/api/v1")
api.Any("/health", healthCheckHandler)

func (*Group) DELETE

func (g *Group) DELETE(path string, handler HandlerFunc, opts ...RouteOption) *route.Route

DELETE adds a DELETE route to the group with the group's prefix.

Example:

api := app.Group("/api/v1")
api.DELETE("/users/:id", deleteUser)

func (*Group) GET

func (g *Group) GET(path string, handler HandlerFunc, opts ...RouteOption) *route.Route

GET adds a GET route to the group with the group's prefix.

Example:

api := app.Group("/api/v1")
api.GET("/users", handler)
api.GET("/users/:id", getUser,
    app.WithDoc(openapi.WithSummary("Get user")),
)

func (*Group) Group

func (g *Group) Group(prefix string, middleware ...HandlerFunc) *Group

Group creates a nested route group under the current group. It combines the parent's prefix with the provided prefix. It inherits middleware from the parent group.

Example:

api := app.Group("/api")
v1 := api.Group("/v1")  // Creates /api/v1 prefix
v1.GET("/users", handler)  // Matches /api/v1/users

func (*Group) HEAD

func (g *Group) HEAD(path string, handler HandlerFunc, opts ...RouteOption) *route.Route

HEAD adds a HEAD route to the group with the group's prefix.

Example:

api := app.Group("/api/v1")
api.HEAD("/users/:id", handler)

func (*Group) OPTIONS

func (g *Group) OPTIONS(path string, handler HandlerFunc, opts ...RouteOption) *route.Route

OPTIONS adds an OPTIONS route to the group with the group's prefix.

Example:

api := app.Group("/api/v1")
api.OPTIONS("/users", handler)

func (*Group) PATCH

func (g *Group) PATCH(path string, handler HandlerFunc, opts ...RouteOption) *route.Route

PATCH adds a PATCH route to the group with the group's prefix.

Example:

api := app.Group("/api/v1")
api.PATCH("/users/:id", patchUser)

func (*Group) POST

func (g *Group) POST(path string, handler HandlerFunc, opts ...RouteOption) *route.Route

POST adds a POST route to the group with the group's prefix.

Example:

api := app.Group("/api/v1")
api.POST("/users", createUser,
    app.WithDoc(
        openapi.WithSummary("Create user"),
        openapi.WithRequest(CreateUserRequest{}),
    ),
)

func (*Group) PUT

func (g *Group) PUT(path string, handler HandlerFunc, opts ...RouteOption) *route.Route

PUT adds a PUT route to the group with the group's prefix.

Example:

api := app.Group("/api/v1")
api.PUT("/users/:id", updateUser)

func (*Group) Use

func (g *Group) Use(middleware ...HandlerFunc)

Use adds middleware to the group that will be executed for all routes in this group. Use middleware is executed after the router's global middleware but before the route-specific handlers.

Example:

api := app.Group("/api")
api.Use(AuthMiddleware(), LoggingMiddleware())
api.GET("/users", getUsersHandler) // Will execute auth + logging + handler

type HandlerFunc

type HandlerFunc func(*Context)

HandlerFunc defines a handler function that receives an Context. HandlerFunc provides access to both router functionality and app-level features like Context.Bind and Context.BindOnly (use Context.Validate after BindOnly when needed).

type HealthOption added in v0.2.0

type HealthOption func(*healthSettings)

HealthOption configures health endpoint settings. These options configure liveness (/livez) and readiness (/readyz) probes.

func WithHealthPrefix added in v0.2.0

func WithHealthPrefix(prefix string) HealthOption

WithHealthPrefix sets the mount prefix for health endpoints. By default, endpoints are mounted at root (e.g., /livez, /readyz). Use this to mount under a different prefix (e.g., /_system/livez).

Example:

app.MustNew(
    app.WithHealthEndpoints(
        app.WithHealthPrefix("/_system"),
    ),
)
// Endpoints: /_system/livez, /_system/readyz

func WithHealthTimeout added in v0.2.0

func WithHealthTimeout(d time.Duration) HealthOption

WithHealthTimeout sets the timeout for each health check. Each check runs with an independent context.WithTimeout to prevent one slow dependency from blocking the entire health check. Default is 1 second.

Example:

app.WithHealthEndpoints(
    app.WithHealthTimeout(500 * time.Millisecond),
)

func WithLivenessCheck added in v0.2.0

func WithLivenessCheck(name string, check CheckFunc) HealthOption

WithLivenessCheck adds a liveness check. Liveness checks determine if the process is alive and should be dependency-free. If any liveness check fails, /livez returns 503.

Multiple calls accumulate checks. If no liveness checks are provided, /livez always returns 200 (process is running).

Example:

app.WithHealthEndpoints(
    app.WithLivenessCheck("process", func(ctx context.Context) error {
        // Dependency-free check: process is alive
        return nil
    }),
    app.WithLivenessCheck("goroutines", func(ctx context.Context) error {
        if runtime.NumGoroutine() > 10000 {
            return errors.New("too many goroutines")
        }
        return nil
    }),
)

func WithLivezPath added in v0.16.0

func WithLivezPath(path string) HealthOption

WithLivezPath sets the path for the liveness probe endpoint. Default is "/livez". The path is appended to the prefix (if set).

Example:

app.WithHealthEndpoints(
    app.WithLivezPath("/live"),
)
// Endpoint: /live (or /{prefix}/live if prefix is set)

func WithReadinessCheck added in v0.2.0

func WithReadinessCheck(name string, check CheckFunc) HealthOption

WithReadinessCheck adds a readiness check. Readiness checks determine if the service is ready to accept traffic. These typically check external dependencies (database, cache, external APIs). If any readiness check fails, /readyz returns 503.

Multiple calls accumulate checks. If no readiness checks are provided, /readyz always returns 204 (service is ready).

Example:

app.WithHealthEndpoints(
    app.WithReadinessCheck("database", func(ctx context.Context) error {
        return db.PingContext(ctx)
    }),
    app.WithReadinessCheck("cache", func(ctx context.Context) error {
        return redis.Ping(ctx).Err()
    }),
    app.WithReadinessCheck("upstream", func(ctx context.Context) error {
        resp, err := http.Get("https://api.example.com/health")
        if err != nil {
            return err
        }
        defer resp.Body.Close()
        if resp.StatusCode != http.StatusOK {
            return errors.New("upstream unhealthy")
        }
        return nil
    }),
)

func WithReadyzPath added in v0.2.0

func WithReadyzPath(path string) HealthOption

WithReadyzPath sets the path for the readiness probe endpoint. Default is "/readyz". The path is appended to the prefix (if set).

Example:

app.WithHealthEndpoints(
    app.WithReadyzPath("/ready"),
)
// Endpoint: /ready (or /{prefix}/ready if prefix is set)

type Hooks

type Hooks struct {
	// contains filtered or unexported fields
}

Hooks manages application lifecycle hooks. It stores callbacks for different lifecycle events.

type LoggingConfig added in v0.5.0

type LoggingConfig struct {
	Handler LoggingHandler `config:"handler" json:"handler" yaml:"handler"`
	Level   LoggingLevel   `config:"level" json:"level" yaml:"level"`
}

LoggingConfig configures structured logging. This struct can be loaded from configuration files (YAML, JSON, etc.).

Example YAML:

logging:
  handler: json
  level: info

type LoggingHandler added in v0.5.0

type LoggingHandler string

LoggingHandler defines log output formats.

const (
	// LoggingConsole uses console handler (human-readable).
	LoggingConsole LoggingHandler = "console"
	// LoggingJSON uses JSON handler (machine-readable).
	LoggingJSON LoggingHandler = "json"
)

type LoggingLevel added in v0.5.0

type LoggingLevel string

LoggingLevel defines log levels.

const (
	// LoggingDebug enables debug-level logging.
	LoggingDebug LoggingLevel = "debug"
	// LoggingInfo enables info-level logging.
	LoggingInfo LoggingLevel = "info"
	// LoggingWarn enables warn-level logging.
	LoggingWarn LoggingLevel = "warn"
	// LoggingError enables error-level logging.
	LoggingError LoggingLevel = "error"
)

type MTLSOption

type MTLSOption func(*mtlsConfig)

MTLSOption configures mtlsConfig.

func WithAuthorize

func WithAuthorize(fn func(*x509.Certificate) (principal string, allowed bool)) MTLSOption

WithAuthorize sets a callback that maps client certificates to principal identity and authorization. The callback returns the principal (e.g., CN, SAN SPIFFE ID) and whether access is allowed. If not set, any valid client certificate is accepted.

Example:

WithAuthorize(func(cert *x509.Certificate) (string, bool) {
    // Extract SPIFFE ID from SAN
    for _, uri := range cert.URIs {
        if uri.Scheme == "spiffe" {
            return uri.String(), true
        }
    }
    // Fallback to CN
    return cert.Subject.CommonName, cert.Subject.CommonName != ""
})

func WithClientCAs

func WithClientCAs(pool *x509.CertPool) MTLSOption

WithClientCAs sets the certificate authority pool for validating client certificates. It is required for mTLS - without it, client certificate validation will fail.

func WithConfigForClient

func WithConfigForClient(fn func(*tls.ClientHelloInfo) (*tls.Config, error)) MTLSOption

WithConfigForClient sets a callback for per-client TLS configuration. Useful for hot-reloading certificates or dynamic configuration. If not set, default configuration is used.

func WithMinVersion

func WithMinVersion(version uint16) MTLSOption

WithMinVersion sets the minimum TLS version to accept. Defaults to TLS 1.3 if not specified. Use TLS 1.2 or lower only if compatibility is required.

func WithSNI

func WithSNI(fn func(*tls.ClientHelloInfo) (*tls.Certificate, error)) MTLSOption

WithSNI sets a callback for SNI (Server Name Indication) support. Allows serving different certificates based on the requested hostname. If not set, ServerCert is used for all connections.

type MetricsConfig added in v0.5.0

type MetricsConfig struct {
	Provider MetricsProvider `config:"provider" json:"provider" yaml:"provider"`
	Endpoint string          `config:"endpoint" json:"endpoint" yaml:"endpoint"`
	Path     string          `config:"path" json:"path" yaml:"path"`
}

MetricsConfig configures metrics collection. This struct can be loaded from configuration files (YAML, JSON, etc.).

Example YAML:

metrics:
  provider: prometheus
  endpoint: ":9090"
  path: /metrics

type MetricsProvider added in v0.5.0

type MetricsProvider string

MetricsProvider defines available metrics backends.

const (
	// MetricsPrometheus uses Prometheus exporter for metrics.
	MetricsPrometheus MetricsProvider = "prometheus"
	// MetricsOTLP uses OTLP HTTP exporter for metrics.
	MetricsOTLP MetricsProvider = "otlp"
	// MetricsStdout uses stdout exporter for metrics (development/testing).
	MetricsStdout MetricsProvider = "stdout"
)

type ObservabilityConfig added in v0.5.0

type ObservabilityConfig struct {
	Tracing TracingConfig `config:"tracing" json:"tracing" yaml:"tracing"`
	Metrics MetricsConfig `config:"metrics" json:"metrics" yaml:"metrics"`
	Logging LoggingConfig `config:"logging" json:"logging" yaml:"logging"`

	ExcludePaths    []string `config:"excludePaths" json:"excludePaths" yaml:"excludePaths"`
	ExcludePrefixes []string `config:"excludePrefixes" json:"excludePrefixes" yaml:"excludePrefixes"`
}

ObservabilityConfig is the unified observability configuration. Embed this in your app config struct for seamless config loading.

Example YAML:

observability:
  tracing:
    provider: otlp
    endpoint: localhost:4317
  metrics:
    provider: prometheus
  logging:
    handler: json
    level: info
  excludePaths:
    - /livez
    - /readyz

Example usage:

type AppConfig struct {
    Server        ServerConfig           `config:"server"`
    Observability app.ObservabilityConfig `config:"observability"`
}

app.New(
    app.WithServiceName("my-api"),
    app.WithObservabilityConfig(cfg.Observability),
)

type ObservabilityOption added in v0.2.0

type ObservabilityOption func(*observabilitySettings)

ObservabilityOption configures unified observability settings. These options configure metrics, tracing, logging, and shared settings like path exclusions.

func WithAccessLogScope added in v0.18.0

func WithAccessLogScope(scope AccessLogScope) ObservabilityOption

WithAccessLogScope sets which requests are logged. When not set, production defaults to errors-only and development to all requests. Invalid scope values cause validation to fail at startup.

Example:

app.WithObservability(
    app.WithAccessLogScope(app.AccessLogScopeErrorsOnly),
)

func WithAccessLogging added in v0.2.0

func WithAccessLogging(enabled bool) ObservabilityOption

WithAccessLogging enables or disables access logging. Default is true.

Example:

app.WithObservability(
    app.WithAccessLogging(false), // Disable access logs
)

func WithExcludePaths added in v0.2.0

func WithExcludePaths(paths ...string) ObservabilityOption

WithExcludePaths adds exact paths to exclude from ALL observability (metrics, tracing, access logging). Multiple calls accumulate paths. Default exclusions are preserved unless WithoutDefaultExclusions() is called first.

Example:

app.WithObservability(
    app.WithExcludePaths("/custom-health", "/k8s-probe"),
)

func WithExcludePatterns added in v0.2.0

func WithExcludePatterns(patterns ...string) ObservabilityOption

WithExcludePatterns adds regex patterns to exclude from ALL observability. Paths matching any pattern will be excluded.

Example:

app.WithObservability(
    app.WithExcludePatterns(`^/v[0-9]+/internal/.*`, `^/debug/.*`),
)

func WithExcludePrefixes added in v0.2.0

func WithExcludePrefixes(prefixes ...string) ObservabilityOption

WithExcludePrefixes adds path prefixes to exclude from ALL observability. Paths starting with any of these prefixes will be excluded.

Example:

app.WithObservability(
    app.WithExcludePrefixes("/internal/", "/admin/", "/debug/"),
)

func WithLogging added in v0.2.0

func WithLogging(opts ...logging.Option) ObservabilityOption

WithLogging enables structured logging with the given options. Service name and version are automatically injected from app-level configuration.

If not provided, a no-op logger is used (logs are discarded).

The app automatically derives request-scoped loggers that include:

  • HTTP metadata (method, route, target path, client IP)
  • Request ID (if X-Request-ID header is present)
  • Trace/span IDs (if OpenTelemetry tracing is enabled)

Example:

app.MustNew(
    app.WithServiceName("orders-api"),
    app.WithServiceVersion("v1.0.0"),
    app.WithObservability(
        app.WithLogging(
            logging.WithJSONHandler(),
            logging.WithDebugLevel(),
            // Service name/version auto-injected from app config
        ),
    ),
)

func WithMetrics

func WithMetrics(opts ...metrics.Option) ObservabilityOption

WithMetrics enables metrics collection with the given options. Service name and version are automatically injected from app-level configuration.

Example:

app.MustNew(
    app.WithServiceName("orders-api"),
    app.WithObservability(
        app.WithMetrics(), // Prometheus is default
    ),
)

func WithMetricsOnMainRouter added in v0.2.0

func WithMetricsOnMainRouter(path string) ObservabilityOption

WithMetricsOnMainRouter mounts the metrics endpoint on the main application router instead of running a separate metrics server.

By default, the metrics package runs a separate server on :9090. Use this option when you need metrics on the same port as the application, such as in Kubernetes environments with strict ingress rules.

The separate metrics server is automatically disabled when this option is used.

Example:

app.MustNew(
    app.WithServiceName("orders-api"),
    app.WithObservability(
        app.WithMetrics(), // Prometheus is default
        app.WithMetricsOnMainRouter("/metrics"),
    ),
)

func WithMetricsSeparateServer added in v0.2.0

func WithMetricsSeparateServer(addr, path string) ObservabilityOption

WithMetricsSeparateServer configures the separate metrics server with custom address and path.

By default, metrics run on a separate server at :9090/metrics. Use this option to customize the port or endpoint path.

This is mutually exclusive with WithMetricsOnMainRouter.

Example:

app.MustNew(
    app.WithServiceName("orders-api"),
    app.WithObservability(
        app.WithMetrics(), // Prometheus is default
        app.WithMetricsSeparateServer(":9091", "/custom-metrics"),
    ),
)

func WithSlowThreshold added in v0.2.0

func WithSlowThreshold(d time.Duration) ObservabilityOption

WithSlowThreshold sets the duration threshold for marking requests as "slow". Slow requests are always logged, even when using AccessLogScopeErrorsOnly. Default is 1 second.

Example:

app.WithObservability(
    app.WithSlowThreshold(500 * time.Millisecond),
)

func WithTracing

func WithTracing(opts ...tracing.Option) ObservabilityOption

WithTracing enables distributed tracing with the given options. Service name and version are automatically injected from app-level configuration.

Example:

app.MustNew(
    app.WithServiceName("orders-api"),
    app.WithObservability(
        app.WithTracing(tracing.WithOTLP("localhost:4317")),
    ),
)

func WithoutDefaultExclusions added in v0.2.0

func WithoutDefaultExclusions() ObservabilityOption

WithoutDefaultExclusions clears the default path exclusions. By default, common health/probe paths are excluded (/health, /livez, /ready, etc.). Use this option to start with an empty exclusion list, then add your own paths.

Example:

app.WithObservability(
    app.WithoutDefaultExclusions(),
    app.WithExcludePaths("/only-this", "/and-that"),
)

type Option

type Option func(*config)

Option defines functional options for app configuration. Option functions are used to configure an App instance during creation.

func WithDebugEndpoints added in v0.2.0

func WithDebugEndpoints(opts ...DebugOption) Option

WithDebugEndpoints enables and configures debug endpoints. By default, no debug features are enabled - you must explicitly opt-in to specific features like pprof for security reasons.

Example:

// Development: full debug access
app.MustNew(
    app.WithServiceName("orders-api"),
    app.WithDebugEndpoints(
        app.WithPprof(),
    ),
)

// Production: conditional debug access
app.MustNew(
    app.WithServiceName("orders-api"),
    app.WithDebugEndpoints(
        app.WithDebugPrefix("/_internal/debug"),
        app.WithPprofIf(os.Getenv("ENABLE_DEBUG") == "true"),
    ),
)

func WithDefaultErrorFormat

func WithDefaultErrorFormat(mediaType string) Option

WithDefaultErrorFormat sets the default format when no Accept header matches. Only used when content-negotiated formatters are configured (via WithErrorFormatterFor with non-empty media types or WithErrorFormatters).

Example:

app.New(
    app.WithErrorFormatterFor("application/problem+json", errors.WithRFC9457("...")),
    app.WithErrorFormatterFor("application/json", errors.WithSimple()),
    app.WithDefaultErrorFormat("application/problem+json"),
)

func WithEnv added in v0.6.0

func WithEnv() Option

WithEnv enables environment variable overrides for framework configuration. Environment variables use the RIVAAS_ prefix and take precedence over programmatic configuration.

Supported variables:

Core:
  RIVAAS_ENV                    - Environment mode: "development" or "production"
  RIVAAS_SERVICE_NAME           - Service name for observability
  RIVAAS_SERVICE_VERSION        - Service version

Server:
  RIVAAS_PORT                   - HTTP server port (e.g., "8080")
  RIVAAS_HOST                   - HTTP server host/interface (e.g., "127.0.0.1")
  RIVAAS_READ_TIMEOUT           - Request read timeout (e.g., "10s")
  RIVAAS_WRITE_TIMEOUT          - Response write timeout (e.g., "10s")
  RIVAAS_SHUTDOWN_TIMEOUT       - Graceful shutdown timeout (e.g., "30s")

Logging:
  RIVAAS_LOG_LEVEL              - Log level: "debug", "info", "warn", "error"
  RIVAAS_LOG_FORMAT             - Log format: "json", "text", or "console"

Observability:
  RIVAAS_METRICS_EXPORTER       - Metrics exporter: "prometheus", "otlp", or "stdout"
  RIVAAS_METRICS_ADDR           - Prometheus address (default: ":9090")
  RIVAAS_METRICS_PATH           - Prometheus path (default: "/metrics")
  RIVAAS_METRICS_ENDPOINT       - OTLP metrics endpoint (e.g., "http://localhost:4318")
  RIVAAS_TRACING_EXPORTER       - Tracing exporter: "otlp", "otlp-http", or "stdout"
  RIVAAS_TRACING_ENDPOINT       - OTLP tracing endpoint (e.g., "localhost:4317")

Debug:
  RIVAAS_PPROF_ENABLED          - Enable pprof: "true" or "false"

Example:

export RIVAAS_ENV=production
export RIVAAS_PORT=3000
export RIVAAS_LOG_LEVEL=warn

app := app.MustNew(
    app.WithServiceName("orders-api"),
    app.WithEnv(),  // Applies environment overrides
)

func WithEnvPrefix added in v0.6.0

func WithEnvPrefix(prefix string) Option

WithEnvPrefix enables environment variable overrides with a custom prefix. Use this when deploying multiple Rivaas services that need different configurations.

The prefix is prepended to the standard variable names. For example, with prefix "ORDERS_":

  • ORDERS_ENV instead of RIVAAS_ENV
  • ORDERS_PORT instead of RIVAAS_PORT

Example:

// Service 1: uses ORDERS_ENV, ORDERS_PORT, etc.
app.MustNew(
    app.WithServiceName("orders-api"),
    app.WithEnvPrefix("ORDERS_"),
)

// Service 2: uses PAYMENTS_ENV, PAYMENTS_PORT, etc.
app.MustNew(
    app.WithServiceName("payments-api"),
    app.WithEnvPrefix("PAYMENTS_"),
)

func WithEnvironment

func WithEnvironment(env string) Option

WithEnvironment sets the environment mode. Valid values are "development" or "production". Invalid values cause validation to fail during New.

Environment affects:

  • Access log scope (when unset via WithAccessLogScope, production defaults to errors-only, development to all)
  • Startup banner (development shows route table)
  • Terminal colors (production strips ANSI sequences)

Example:

app.New(app.WithEnvironment("production"))

func WithErrorFormatterFor added in v0.20.0

func WithErrorFormatterFor(mediaType string, opts ...errors.Option) Option

WithErrorFormatterFor configures an error formatter from options. The app builds the formatter via errors.New(opts...); invalid options are reported during config validation.

Use empty mediaType ("") for a single formatter for all responses (no content negotiation). Use a non-empty mediaType (e.g. "application/problem+json") to register a formatter for content negotiation; multiple calls accumulate. Cannot mix: use either a single formatter ("") or content-negotiated formatters, not both.

Example:

// Single formatter for all responses
app.New(
    app.WithServiceName("my-service"),
    app.WithErrorFormatterFor("", errors.WithRFC9457("https://api.example.com/problems")),
)

// Content negotiation by Accept header
app.New(
    app.WithServiceName("my-service"),
    app.WithErrorFormatterFor("application/problem+json", errors.WithRFC9457("https://api.example.com/problems")),
    app.WithErrorFormatterFor("application/json", errors.WithSimple()),
    app.WithDefaultErrorFormat("application/problem+json"),
)

func WithErrorFormatters

func WithErrorFormatters(formatters map[string]errors.Formatter) Option

WithErrorFormatters configures multiple error formatters with content negotiation by Accept header. Advanced: use when you need to pass pre-built or custom formatters. Prefer WithErrorFormatterFor for option-based configuration.

Example:

app.New(
    app.WithServiceName("my-service"),
    app.WithErrorFormatters(map[string]errors.Formatter{
        "application/problem+json": errors.MustNew(errors.WithRFC9457("https://api.example.com/problems")),
        "application/json":        errors.MustNew(errors.WithSimple()),
    }),
    app.WithDefaultErrorFormat("application/problem+json"),
)

func WithHealthEndpoints added in v0.2.0

func WithHealthEndpoints(opts ...HealthOption) Option

WithHealthEndpoints enables and configures health check endpoints. This registers /livez (liveness) and /readyz (readiness) endpoints.

Endpoints registered:

  • GET /livez (or /{prefix}/livez) - Liveness probe Returns 200 "ok" if all liveness checks pass, 503 if any fail
  • GET /readyz (or /{prefix}/readyz) - Readiness probe Returns 204 if all readiness checks pass, 503 if any fail

Example:

app.MustNew(
    app.WithServiceName("orders-api"),
    app.WithHealthEndpoints(
        app.WithHealthPrefix("/_system"),
        app.WithHealthTimeout(800 * time.Millisecond),
        app.WithLivenessCheck("process", func(ctx context.Context) error {
            return nil // Process is alive
        }),
        app.WithReadinessCheck("database", func(ctx context.Context) error {
            return db.PingContext(ctx)
        }),
        app.WithReadinessCheck("cache", func(ctx context.Context) error {
            return redis.Ping(ctx).Err()
        }),
    ),
)

func WithHost added in v0.6.0

func WithHost(host string) Option

WithHost sets the host/interface to bind the HTTP server to. Default is "" (all interfaces, equivalent to "0.0.0.0"). Use "127.0.0.1" or "localhost" to restrict to local connections only.

Example:

// Bind to all interfaces (default)
app.New(app.WithPort(8080))

// Bind to localhost only (e.g., behind reverse proxy)
app.New(
    app.WithHost("127.0.0.1"),
    app.WithPort(8080),
)

func WithMTLS added in v0.18.0

func WithMTLS(serverCert tls.Certificate, opts ...MTLSOption) Option

WithMTLS configures the server to serve HTTPS with mutual TLS (mTLS) using the given server certificate and options. Only one of WithTLS or WithMTLS may be used. Default listen port is 8443 unless overridden by WithPort or RIVAAS_PORT when WithEnv is used.

Example:

serverCert, _ := tls.LoadX509KeyPair("server.crt", "server.key")
app.New(
    app.WithServiceName("my-api"),
    app.WithMTLS(serverCert,
        app.WithClientCAs(caCertPool),
        app.WithMinVersion(tls.VersionTLS13),
    ), // default port 8443; use WithPort(443) to override
)
// ...
app.Start(ctx)

func WithMiddleware

func WithMiddleware(middlewares ...HandlerFunc) Option

WithMiddleware adds middleware during app initialization. Middleware provided here will be added before any middleware added via Use(). Multiple calls to WithMiddleware are supported and will accumulate.

Note: This does not affect default middleware (recovery). Use WithoutDefaultMiddleware() to disable default middleware.

Example:

app.New(
    app.WithServiceName("my-service"),
    app.WithMiddleware(
        middleware.Logger(),
        middleware.Recovery(),
    ),
)

func WithObservability added in v0.2.0

func WithObservability(opts ...ObservabilityOption) Option

WithObservability configures all observability components: metrics, tracing, and logging. This is the single entry point for configuring the three pillars of observability.

Components:

  • WithLogging: enables structured logging (service name/version auto-injected)
  • WithMetrics: enables metrics collection (Prometheus, OTLP)
  • WithTracing: enables distributed tracing (OTLP, Jaeger)

Shared settings (apply to all components):

  • WithExcludePaths, WithExcludePrefixes, WithExcludePatterns, WithoutDefaultExclusions
  • WithAccessLogging, WithAccessLogScope, WithSlowThreshold

Default exclusions include common health/probe paths: /health, /livez, /ready, /readyz, /live, /metrics, /debug/*

Example:

app.MustNew(
    app.WithServiceName("orders-api"),
    app.WithServiceVersion("v1.0.0"),
    app.WithObservability(
        app.WithLogging(logging.WithJSONHandler(), logging.WithDebugLevel()),
        app.WithMetrics(), // Prometheus is default; use metrics.WithOTLP() for OTLP
        app.WithTracing(tracing.WithOTLP("localhost:4317")),
        app.WithExcludePaths("/custom-health"),
        app.WithExcludePrefixes("/internal/", "/admin/"),
        app.WithAccessLogScope(app.AccessLogScopeErrorsOnly),
        app.WithSlowThreshold(500 * time.Millisecond),
    ),
)

func WithObservabilityFromConfig added in v0.5.0

func WithObservabilityFromConfig(cfg ObservabilityConfig) Option

WithObservabilityFromConfig configures all observability from a single config struct. This is a convenience method that converts declarative configuration into functional options and applies them via the existing WithObservability function.

This function is ideal for loading observability configuration from files (YAML, JSON, etc.). Invalid tracing, metrics, or logging configuration is reported when the app is constructed (e.g. from New) as a validation error, not via panic.

Example:

app.New(
    app.WithServiceName("blog-api"),
    app.WithObservabilityFromConfig(cfg.Observability),
)

func WithOpenAPI

func WithOpenAPI(opts ...openapi.Option) Option

WithOpenAPI enables OpenAPI specification generation with the given options. Service name and version are automatically injected from app-level configuration after all options are applied (in config validation), so option order does not matter. If not explicitly set via openapi.WithTitle(), the app's service name and version are used.

Example:

app.New(
    app.WithServiceName("my-service"),
    app.WithServiceVersion("v1.0.0"),
    app.WithOpenAPI(
        openapi.WithTitle("My API", "1.0.0"),
        openapi.WithDescription("API description"),
        openapi.WithBearerAuth("bearerAuth", "JWT authentication"),
        openapi.WithServer("http://localhost:8080", "Local development"),
        openapi.WithSwaggerUI(true, "/docs"),
        openapi.WithUIDocExpansion(openapi.DocExpansionList),
        openapi.WithUISyntaxTheme(openapi.SyntaxThemeMonokai),
    ),
)

func WithPort added in v0.6.0

func WithPort(port int) Option

WithPort sets the server listen port. Default is 8080 for HTTP; when using WithTLS or WithMTLS the default is 8443. Override with WithPort(n) in all cases. Can be overridden by RIVAAS_PORT when WithEnv is used.

Example:

app.New(app.WithPort(3000))

func WithRouter added in v0.5.0

func WithRouter(opts ...router.Option) Option

WithRouter passes router options through to the underlying router.

Example:

app := app.New(
    app.WithServiceName("my-service"),
    app.WithRouter(
        router.WithBloomFilterSize(2000),
        router.WithoutCancellationCheck(),
        router.WithTemplateRouting(true),
        router.WithVersioning(),
    ),
)

Multiple calls to WithRouter accumulate options.

func WithServer added in v0.5.0

func WithServer(opts ...ServerOption) Option

WithServer configures server settings using functional options.

Example:

app.New(
    app.WithServer(
        app.WithReadTimeout(15 * time.Second),
        app.WithWriteTimeout(15 * time.Second),
        app.WithShutdownTimeout(30 * time.Second),
    ),
)

func WithServiceName

func WithServiceName(name string) Option

WithServiceName sets the service name used in observability metadata. An empty name causes validation to fail during New.

Example:

app.New(app.WithServiceName("my-api"))

func WithServiceVersion

func WithServiceVersion(version string) Option

WithServiceVersion sets the service version used in observability metadata. An empty version causes validation to fail during New.

Example:

app.New(app.WithServiceVersion("v1.0.0"))

func WithTLS added in v0.18.0

func WithTLS(certFile, keyFile string) Option

WithTLS configures the server to serve HTTPS using the given certificate and key files. Only one of WithTLS or WithMTLS may be used. Both certFile and keyFile must be non-empty. Default listen port is 8443 unless overridden by WithPort or RIVAAS_PORT when WithEnv is used.

Example:

app.New(
    app.WithServiceName("my-api"),
    app.WithTLS("server.crt", "server.key"), // default port 8443; use WithPort(443) to override
)
// ...
app.Start(ctx)

func WithValidationEngine added in v0.20.0

func WithValidationEngine(engine *validation.Engine) Option

WithValidationEngine sets the validation engine used by Context.Bind and Context.Validate. When set, the app uses this engine instead of the package-level validation.DefaultEngine. Use this for custom validation configuration (e.g. redaction, MaxErrors) or test isolation.

Example:

engine := validation.MustNew(validation.WithRedactor(myRedactor))
app := app.MustNew(
    app.WithServiceName("my-api"),
    app.WithValidationEngine(engine),
)

func WithoutDefaultMiddleware added in v0.2.0

func WithoutDefaultMiddleware() Option

WithoutDefaultMiddleware disables the default middleware (recovery). Use this when you want full control over middleware and don't want the framework to automatically add recovery middleware.

Example:

app.New(
    app.WithServiceName("my-service"),
    app.WithoutDefaultMiddleware(),
    app.WithMiddleware(myCustomRecovery), // Add your own
)

type ReadinessManager

type ReadinessManager struct {
	// contains filtered or unexported fields
}

ReadinessManager manages readiness gates for runtime health checks. ReadinessManager is safe for concurrent use by multiple goroutines.

This complements the static WithReadinessCheck options by allowing dynamic registration and unregistration of readiness gates at runtime.

Typical use cases:

  • Database connection pools that manage their own health
  • External service clients with retry/circuit breaker logic
  • Components that need to temporarily mark themselves as not ready

func (*ReadinessManager) Check

func (rm *ReadinessManager) Check() (bool, map[string]bool)

Check checks if all registered gates are ready. Check returns true if all gates are ready, false otherwise. Check also returns a map of gate names to their readiness status.

Example:

ready, status := app.Readiness().Check()
if !ready {
    for name, isReady := range status {
        if !isReady {
            log.Printf("Gate %s is not ready", name)
        }
    }
}

func (*ReadinessManager) Register

func (rm *ReadinessManager) Register(name string, gate Gate)

Register registers a readiness gate at runtime. If a gate with the same name already exists, it is replaced.

Example:

type DatabaseGate struct {
    db *sql.DB
}
func (g *DatabaseGate) Ready() bool {
    return g.db.Ping() == nil
}
func (g *DatabaseGate) Name() string { return "database" }

app.Readiness().Register("db", &DatabaseGate{db: db})

func (*ReadinessManager) Unregister

func (rm *ReadinessManager) Unregister(name string)

Unregister removes a readiness gate by name. This is useful when a component is being shut down or is no longer relevant to the application's readiness.

Example:

// During graceful shutdown of a specific component
app.Readiness().Unregister("database")

type RouteOption added in v0.4.0

type RouteOption func(*routeConfig)

RouteOption configures a route. Options can configure middleware (before/after), documentation, or combine multiple options. This follows the functional options pattern used throughout the framework.

func RouteOptions added in v0.4.0

func RouteOptions(opts ...RouteOption) RouteOption

RouteOptions combines multiple options into a single option. This is useful for creating reusable option sets.

Example:

var Authenticated = app.RouteOptions(
    app.WithBefore(authMiddleware),
    app.WithDoc(
        openapi.Security("bearerAuth"),
        openapi.Response(401, UnauthorizedError{}),
    ),
)

app.GET("/users/:id", getUser,
    Authenticated,
    app.WithDoc(
        openapi.Summary("Get user"),
        openapi.Response(200, UserResponse{}),
    ),
)

func WithAfter added in v0.4.0

func WithAfter(handlers ...HandlerFunc) RouteOption

WithAfter adds post-handler middleware to the route. Middleware added with WithAfter executes after the main handler.

Example:

app.GET("/users/:id", getUser,
    app.WithAfter(auditLogMiddleware, metricsMiddleware),
)

func WithBefore added in v0.4.0

func WithBefore(handlers ...HandlerFunc) RouteOption

WithBefore adds pre-handler middleware to the route. Middleware added with WithBefore executes before the main handler.

Example:

app.GET("/users/:id", getUser,
    app.WithBefore(authMiddleware, rateLimitMiddleware),
)

func WithDoc added in v0.4.0

func WithDoc(opts ...openapi.OperationOption) RouteOption

WithDoc adds OpenAPI documentation to the route. Documentation options are provided by the openapi package.

Example:

app.GET("/users/:id", getUser,
    app.WithDoc(
        openapi.Summary("Get user"),
        openapi.Description("Retrieves a user by ID"),
        openapi.Response(200, UserResponse{}),
        openapi.Response(404, ErrorResponse{}),
        openapi.Tags("users"),
    ),
)

func WithoutDoc added in v0.4.0

func WithoutDoc() RouteOption

WithoutDoc explicitly disables documentation for this route. This is useful when global documentation is enabled but specific routes should be excluded.

Example:

app.GET("/health", healthCheck,
    app.WithoutDoc(),
)

type ServerOption

type ServerOption func(*serverConfig)

ServerOption configures server settings.

func WithIdleTimeout

func WithIdleTimeout(d time.Duration) ServerOption

WithIdleTimeout sets the server idle timeout. WithIdleTimeout configures how long the server waits for the next request on a keep-alive connection.

Example:

app.New(
    app.WithServer(
        app.WithIdleTimeout(60 * time.Second),
    ),
)

func WithMaxHeaderBytes

func WithMaxHeaderBytes(n int) ServerOption

WithMaxHeaderBytes sets the maximum size of request headers. WithMaxHeaderBytes configures the maximum number of bytes allowed in request headers.

Example:

app.New(
    app.WithServer(
        app.WithMaxHeaderBytes(1 << 20), // 1MB
    ),
)

func WithReadHeaderTimeout

func WithReadHeaderTimeout(d time.Duration) ServerOption

WithReadHeaderTimeout sets the server read header timeout. WithReadHeaderTimeout configures how long the server waits to read request headers.

Example:

app.New(
    app.WithServer(
        app.WithReadHeaderTimeout(2 * time.Second),
    ),
)

func WithReadTimeout

func WithReadTimeout(d time.Duration) ServerOption

WithReadTimeout sets the server read timeout. WithReadTimeout configures how long the server waits to read the entire request.

Example:

app.New(
    app.WithServer(
        app.WithReadTimeout(10 * time.Second),
    ),
)

func WithShutdownTimeout

func WithShutdownTimeout(d time.Duration) ServerOption

WithShutdownTimeout sets the graceful shutdown timeout. WithShutdownTimeout configures how long the server waits for graceful shutdown to complete.

Example:

app.New(
    app.WithServer(
        app.WithShutdownTimeout(30 * time.Second),
    ),
)

func WithWriteTimeout

func WithWriteTimeout(d time.Duration) ServerOption

WithWriteTimeout sets the server write timeout. WithWriteTimeout configures how long the server waits to write the response.

Example:

app.New(
    app.WithServer(
        app.WithWriteTimeout(10 * time.Second),
    ),
)

type TestOption

type TestOption func(*testConfig)

TestOption configures test execution behavior.

func WithContext

func WithContext(ctx context.Context) TestOption

WithContext uses the provided context for the test request. Useful for testing context propagation and cancellation.

func WithTimeout

func WithTimeout(d time.Duration) TestOption

WithTimeout sets the test request timeout. Use -1 for no timeout.

Example:

resp, err := app.Test(req, WithTimeout(5*time.Second))

type TracingConfig added in v0.5.0

type TracingConfig struct {
	Provider   TracingProvider `config:"provider" json:"provider" yaml:"provider"`
	Endpoint   string          `config:"endpoint" json:"endpoint" yaml:"endpoint"`
	SampleRate float64         `config:"sampleRate" json:"sampleRate" yaml:"sampleRate"`
	Insecure   bool            `config:"insecure" json:"insecure" yaml:"insecure"`
}

TracingConfig configures distributed tracing. This struct can be loaded from configuration files (YAML, JSON, etc.).

Example YAML:

tracing:
  provider: otlp
  endpoint: localhost:4317
  sampleRate: 0.1
  insecure: true

type TracingProvider added in v0.5.0

type TracingProvider string

TracingProvider defines available tracing backends.

const (
	// TracingStdout exports traces to stdout (development/testing).
	TracingStdout TracingProvider = "stdout"
	// TracingOTLP exports traces via OTLP gRPC protocol.
	TracingOTLP TracingProvider = "otlp"
	// TracingOTLPHTTP exports traces via OTLP HTTP protocol.
	TracingOTLPHTTP TracingProvider = "otlp-http"
	// TracingNoop is a no-op provider (no traces exported).
	TracingNoop TracingProvider = "noop"
)

type ValidateOption added in v0.20.0

type ValidateOption func(*validateConfig)

ValidateOption configures Context.Validate behavior. ValidateOptions can be passed to Context.Validate.

func WithValidateOptions added in v0.20.0

func WithValidateOptions(opts ...validation.Option) ValidateOption

WithValidateOptions passes options directly to the validation package for this validate call. Use for advanced validation configuration when using Context.Validate.

Example:

if err := c.Validate(&req,
    app.WithValidatePartial(),
    app.WithValidateOptions(validation.WithMaxErrors(5)),
); err != nil {
    c.Fail(err)
    return
}

func WithValidatePartial added in v0.20.0

func WithValidatePartial() ValidateOption

WithValidatePartial enables partial validation for this validate call. Only fields present in the request (or in the presence map) are validated; "required" is ignored for absent fields. Use after Context.BindOnly for PATCH-style flows.

Example:

if err := c.Validate(&req, app.WithValidatePartial()); err != nil {
    c.Fail(err)
    return
}

func WithValidateStrict added in v0.20.0

func WithValidateStrict() ValidateOption

WithValidateStrict disallows unknown fields for this validate call. Use when validating JSON-backed structs and you want to reject unknown keys.

Example:

if err := c.Validate(&req, app.WithValidateStrict()); err != nil {
    c.Fail(err)
    return
}

type VersionGroup

type VersionGroup struct {
	// contains filtered or unexported fields
}

VersionGroup represents a version-specific route group that allows organizing related routes under a specific API version. VersionGroup supports app.HandlerFunc (with app.Context), providing access to binding, validation, and logging features.

Routes registered in a VersionGroup are automatically scoped to that version. The version is detected from the request path, headers, query parameters, or other configured versioning strategies.

Example:

v1 := app.Version("v1")
v1.GET("/status", handlers.Status)      // handler receives *app.Context
v1.POST("/users", handlers.CreateUser)  // handler receives *app.Context

func (*VersionGroup) Any

func (vg *VersionGroup) Any(path string, handler HandlerFunc, opts ...RouteOption) *route.Route

Any registers a route that matches all HTTP methods. It is useful for catch-all endpoints like health checks or proxies.

It registers 7 separate routes internally (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS). For endpoints that only need specific methods, use individual method registrations (GET, POST, etc.).

Returns the GET route (most common for docs/constraints).

Example:

v1.Any("/health", healthCheckHandler)

func (*VersionGroup) DELETE

func (vg *VersionGroup) DELETE(path string, handler HandlerFunc, opts ...RouteOption) *route.Route

DELETE adds a DELETE route to the version group.

Example:

v1.DELETE("/users/:id", deleteUser).WhereInt("id")

func (*VersionGroup) GET

func (vg *VersionGroup) GET(path string, handler HandlerFunc, opts ...RouteOption) *route.Route

GET adds a GET route to the version group.

Example:

v1.GET("/users/:id", handler).WhereInt("id")
v1.GET("/users/:id", getUser,
    app.WithDoc(openapi.WithSummary("Get user")),
)

func (*VersionGroup) Group

func (vg *VersionGroup) Group(prefix string, middleware ...HandlerFunc) *VersionGroup

Group creates a nested version group under the current version group. It combines the parent's prefix with the provided prefix. It inherits middleware from the parent group.

Example:

v1 := app.Version("v1")
api := v1.Group("/api", AuthMiddleware())  // Creates /api prefix within v1
api.GET("/users", handler)                 // Matches /api/users in v1

func (*VersionGroup) HEAD

func (vg *VersionGroup) HEAD(path string, handler HandlerFunc, opts ...RouteOption) *route.Route

HEAD adds a HEAD route to the version group.

Example:

v1.HEAD("/users/:id", handler).WhereInt("id")

func (*VersionGroup) OPTIONS

func (vg *VersionGroup) OPTIONS(path string, handler HandlerFunc, opts ...RouteOption) *route.Route

OPTIONS adds an OPTIONS route to the version group.

Example:

v1.OPTIONS("/users", handler)

func (*VersionGroup) PATCH

func (vg *VersionGroup) PATCH(path string, handler HandlerFunc, opts ...RouteOption) *route.Route

PATCH adds a PATCH route to the version group.

Example:

v1.PATCH("/users/:id", patchUser).WhereInt("id")

func (*VersionGroup) POST

func (vg *VersionGroup) POST(path string, handler HandlerFunc, opts ...RouteOption) *route.Route

POST adds a POST route to the version group.

Example:

v1.POST("/users", createUser,
    app.WithDoc(
        openapi.WithSummary("Create user"),
        openapi.WithRequest(CreateUserRequest{}),
    ),
)

func (*VersionGroup) PUT

func (vg *VersionGroup) PUT(path string, handler HandlerFunc, opts ...RouteOption) *route.Route

PUT adds a PUT route to the version group.

Example:

v1.PUT("/users/:id", updateUser).WhereInt("id")

func (*VersionGroup) Use

func (vg *VersionGroup) Use(middleware ...HandlerFunc)

Use adds middleware to the version group that will be executed for all routes in this group. Middleware is executed after the router's global middleware but before the route-specific handlers.

It applies middleware to all later routes registered in this version group.

Example:

v1 := app.Version("v1")
v1.Use(AuthMiddleware(), LoggingMiddleware())
v1.GET("/users", getUsersHandler) // Will execute auth + logging + handler

Directories

Path Synopsis
examples
01-quick-start command
Package main demonstrates a quick start example of the Rivaas router.
Package main demonstrates a quick start example of the Rivaas router.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL