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

Return to the regular view of this page.

App

API reference for the Rivaas App package - a batteries-included web framework with integrated observability.

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

Overview

The App package provides a high-level, opinionated framework built on top of the Rivaas router. It includes:

  • Integrated observability (metrics, tracing, logging)
  • Lifecycle management with hooks
  • Graceful shutdown handling
  • Health and debug endpoints
  • OpenAPI spec generation
  • Request binding and validation

Package Information

  • Import Path: rivaas.dev/app
  • Go Version: 1.25+
  • License: Apache 2.0

Architecture

┌─────────────────────────────────────────┐
│           Application Layer             │
│  (app 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)                             │
└─────────────────────────────────────────┘

Quick Reference

Core Types

  • App - Main application type
  • Context - Request context with app-level features
  • HandlerFunc - Handler function type

Key Functions

  • New() - Create a new app (returns error)
  • MustNew() - Create a new app (panics on error)

Configuration

API Reference

Resources

App Type

The main application type that wraps the router with app-level features.

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

Creating Apps

// Returns (*App, error) for error handling
a, err := app.New(
    app.WithServiceName("my-api"),
    app.WithServiceVersion("v1.0.0"),
)
if err != nil {
    log.Fatal(err)
}

// Panics on error (like regexp.MustCompile)
a := app.MustNew(
    app.WithServiceName("my-api"),
)

HTTP Methods

Register routes for HTTP methods:

a.GET(path string, handler HandlerFunc, opts ...RouteOption) *route.Route
a.POST(path string, handler HandlerFunc, opts ...RouteOption) *route.Route
a.PUT(path string, handler HandlerFunc, opts ...RouteOption) *route.Route
a.DELETE(path string, handler HandlerFunc, opts ...RouteOption) *route.Route
a.PATCH(path string, handler HandlerFunc, opts ...RouteOption) *route.Route
a.HEAD(path string, handler HandlerFunc, opts ...RouteOption) *route.Route
a.OPTIONS(path string, handler HandlerFunc, opts ...RouteOption) *route.Route
a.Any(path string, handler HandlerFunc, opts ...RouteOption) *route.Route

Only the listed methods are supported; unsupported method causes a panic. See API Reference for details.

Server Management

a.Start(ctx context.Context) error

Configure HTTP, HTTPS, or mTLS at construction with WithTLS or WithMTLS.

Lifecycle Hooks

a.OnStart(fn func(context.Context) error) error
a.OnReady(fn func()) error
a.OnShutdown(fn func(context.Context)) error
a.OnStop(fn func()) error
a.OnRoute(fn func(*route.Route)) error
a.OnReload(fn func(context.Context) error) error

See Lifecycle Hooks for details.

Context Type

Request context that extends router.Context with app-level features.

type Context struct {
    *router.Context
    // contains filtered or unexported fields
}

Request Binding

c.Bind(out any, opts ...BindOption) error
c.MustBind(out any, opts ...BindOption) bool
c.BindOnly(out any, opts ...BindOption) error
c.Validate(v any, opts ...validation.Option) error

Error Handling

c.Fail(err error)
c.FailStatus(status int, err error)
c.NotFound(err error)
c.BadRequest(err error)
c.Unauthorized(err error)
c.Forbidden(err error)
c.Conflict(err error)
c.Gone(err error)
c.UnprocessableEntity(err error)
c.TooManyRequests(err error)
c.InternalError(err error)
c.ServiceUnavailable(err error)

Logging

c.Logger() *slog.Logger

See Context API for complete reference.

HandlerFunc

Handler function type that receives an app Context.

type HandlerFunc func(*Context)

Example:

func handler(c *app.Context) {
    c.JSON(http.StatusOK, data)
}

a.GET("/", handler)

Next Steps

1 - API Reference

Complete API reference for the App package.

Core Functions

New

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

Creates a new App instance with the given options. Returns an error if configuration is invalid.

Parameters:

  • opts - Configuration options

Returns:

  • *App - The app instance
  • error - Configuration validation errors

Example:

a, err := app.New(
    app.WithServiceName("my-api"),
    app.WithServiceVersion("v1.0.0"),
)
if err != nil {
    log.Fatal(err)
}

MustNew

func MustNew(opts ...Option) *App

Creates a new App instance or panics on error. Use for initialization in main() functions.

Parameters:

  • opts - Configuration options

Returns:

  • *App - The app instance

Panics: If configuration is invalid

Example:

a := app.MustNew(
    app.WithServiceName("my-api"),
)

App Methods

HTTP Method Shortcuts

func (a *App) GET(path string, handler HandlerFunc, opts ...RouteOption) *route.Route
func (a *App) POST(path string, handler HandlerFunc, opts ...RouteOption) *route.Route
func (a *App) PUT(path string, handler HandlerFunc, opts ...RouteOption) *route.Route
func (a *App) DELETE(path string, handler HandlerFunc, opts ...RouteOption) *route.Route
func (a *App) PATCH(path string, handler HandlerFunc, opts ...RouteOption) *route.Route
func (a *App) HEAD(path string, handler HandlerFunc, opts ...RouteOption) *route.Route
func (a *App) OPTIONS(path string, handler HandlerFunc, opts ...RouteOption) *route.Route
func (a *App) Any(path string, handler HandlerFunc, opts ...RouteOption) *route.Route

Register routes for HTTP methods. Supported methods are GET, POST, PUT, DELETE, PATCH, HEAD, and OPTIONS. Registering a route with any other method (e.g. CONNECT, TRACE, or a typo) causes a panic with a clear message (fail-fast). The same applies to routes registered via Group and VersionGroup (e.g. Group.GET, VersionGroup.POST).

Middleware

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

Adds middleware to the app. Middleware executes for all routes registered after Use().

Route Groups

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

Create route groups and version groups.

Static Files

func (a *App) Static(prefix, root string)
func (a *App) File(path, filepath string)
func (a *App) StaticFS(prefix string, fs http.FileSystem)
func (a *App) NoRoute(handler HandlerFunc)

Serve static files and set custom 404 handler.

Server Management

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

Starts the server with graceful shutdown. 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.

Lifecycle Hooks

func (a *App) OnStart(fn func(context.Context) error)
func (a *App) OnReady(fn func())
func (a *App) OnShutdown(fn func(context.Context))
func (a *App) OnStop(fn func())
func (a *App) OnRoute(fn func(*route.Route))

Register lifecycle hooks. See Lifecycle Hooks for details.

Accessors

func (a *App) Router() *router.Router
func (a *App) Metrics() *metrics.Recorder
func (a *App) Tracing() *tracing.Tracer
func (a *App) Readiness() *ReadinessManager
func (a *App) ServiceName() string
func (a *App) ServiceVersion() string
func (a *App) Environment() string

Access underlying components and configuration.

Route Management

func (a *App) Route(name string) (*route.Route, bool)
func (a *App) Routes() []*route.Route
func (a *App) URLFor(routeName string, params map[string]string, query map[string][]string) (string, error)
func (a *App) MustURLFor(routeName string, params map[string]string, query map[string][]string) string

Route lookup and URL generation. Router must be frozen (after Start()).

Metrics

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

Access metrics handler and server address.

Logging

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

Returns the application’s base logger. Never returns nil.

Testing

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

Test routes without starting a server.

Helper Functions

ExpectJSON

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

Test helper that asserts response status and decodes JSON.

Generic Binding

func Bind[T any](c *Context, opts ...BindOption) (T, error)
func MustBind[T any](c *Context, opts ...BindOption) (T, bool)
func BindOnly[T any](c *Context, opts ...BindOption) (T, error)
func BindPatch[T any](c *Context, opts ...BindOption) (T, error)
func MustBindPatch[T any](c *Context, opts ...BindOption) (T, bool)
func BindStrict[T any](c *Context, opts ...BindOption) (T, error)
func MustBindStrict[T any](c *Context, opts ...BindOption) (T, bool)

Type-safe binding with generics. These functions provide a more concise API compared to the Context methods.

Types

HandlerFunc

type HandlerFunc func(*Context)

Handler function that receives an app Context.

TestOption

type TestOption func(*testConfig)

func WithTimeout(d time.Duration) TestOption
func WithContext(ctx context.Context) TestOption

Options for testing.

Next Steps

2 - Configuration Options

App-level configuration options reference.

Service Configuration

WithServiceName

func WithServiceName(name string) Option

Sets the service name used in observability metadata. This includes metrics, traces, and logs. If empty, validation fails.

Default: "rivaas-app"

WithServiceVersion

func WithServiceVersion(version string) Option

Sets the service version used in observability and API documentation. Must be non-empty or validation fails.

Default: "1.0.0"

WithEnvironment

func WithEnvironment(env string) Option

Sets the environment mode. Valid values: "development", "production". Invalid values cause validation to fail. When access log scope is not set via WithAccessLogScope, production defaults to errors-only and development to full access logs.

Default: "development"

Server Configuration

WithPort

func WithPort(port int) Option

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.

WithServer

func WithServer(opts ...ServerOption) Option

Configures server settings. See Server Options for sub-options.

Server Transport

At most one of WithTLS or WithMTLS may be used. Configure transport at construction; Start then runs the server. Default listen port for TLS/mTLS is 8443 unless overridden by WithPort or RIVAAS_PORT.

WithTLS

func WithTLS(certFile, keyFile string) Option

Configures the server to serve HTTPS using the given certificate and key files. Both certFile and keyFile must be non-empty. Default port is 8443 unless overridden. See Server guide for examples.

WithMTLS

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

Configures the server to serve HTTPS with mutual TLS (mTLS). Requires a server certificate and typically WithClientCAs for client verification. Default port is 8443 unless overridden. See Server guide for mTLS options and examples.

Observability

WithObservability

func WithObservability(opts ...ObservabilityOption) Option

Configures all observability components (metrics, tracing, logging). See Observability Options for sub-options.

Endpoints

WithHealthEndpoints

func WithHealthEndpoints(opts ...HealthOption) Option

Enables health endpoints. See Health Options for sub-options.

WithDebugEndpoints

func WithDebugEndpoints(opts ...DebugOption) Option

Enables debug endpoints. See Debug Options for sub-options.

Middleware

WithMiddleware

func WithMiddleware(middlewares ...HandlerFunc) Option

Adds middleware during app initialization. Multiple calls accumulate.

WithoutDefaultMiddleware

func WithoutDefaultMiddleware() Option

Disables default middleware (recovery). Use when you want full control over middleware.

Router

WithRouter

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

Passes router options to the underlying router. Multiple calls accumulate.

OpenAPI

WithOpenAPI

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

Enables OpenAPI specification generation. Service name and version are automatically injected from app-level configuration.

Error Formatting

WithErrorFormatter

func WithErrorFormatter(formatter errors.Formatter) Option

Configures a single error formatter for all error responses.

WithErrorFormatters

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

Configures multiple error formatters with content negotiation based on Accept header.

WithDefaultErrorFormat

func WithDefaultErrorFormat(mediaType string) Option

Sets the default format when no Accept header matches. Only used with WithErrorFormatters.

Complete Example

a, err := app.New(
    // Service
    app.WithServiceName("orders-api"),
    app.WithServiceVersion("v2.0.0"),
    app.WithEnvironment("production"),
    
    // Server
    app.WithServer(
        app.WithReadTimeout(10 * time.Second),
        app.WithWriteTimeout(15 * time.Second),
        app.WithShutdownTimeout(30 * time.Second),
    ),
    
    // Observability
    app.WithObservability(
        app.WithLogging(logging.WithJSONHandler()),
        app.WithMetrics(),
        app.WithTracing(tracing.WithOTLP("localhost:4317")),
    ),
    
    // Health endpoints
    app.WithHealthEndpoints(
        app.WithReadinessCheck("database", dbCheck),
    ),
    
    // OpenAPI
    app.WithOpenAPI(
        openapi.WithSwaggerUI(true, "/docs"),
    ),
)

Next Steps

3 - Server Options

Server configuration options reference.

Server Options

These options are used with WithServer():

app.WithServer(
    app.WithReadTimeout(10 * time.Second),
    app.WithWriteTimeout(15 * time.Second),
)

Timeout Options

WithReadTimeout

func WithReadTimeout(d time.Duration) ServerOption

Maximum time to read entire request (including body). Must be positive.

Default: 10s

WithWriteTimeout

func WithWriteTimeout(d time.Duration) ServerOption

Maximum time to write response. Must be positive. Should be >= ReadTimeout.

Default: 10s

WithIdleTimeout

func WithIdleTimeout(d time.Duration) ServerOption

Maximum time to wait for next request on keep-alive connection. Must be positive.

Default: 60s

WithReadHeaderTimeout

func WithReadHeaderTimeout(d time.Duration) ServerOption

Maximum time to read request headers. Must be positive.

Default: 2s

WithShutdownTimeout

func WithShutdownTimeout(d time.Duration) ServerOption

Graceful shutdown timeout. Must be at least 1 second.

Default: 30s

Size Options

WithMaxHeaderBytes

func WithMaxHeaderBytes(n int) ServerOption

Maximum request header size in bytes. Must be at least 1KB (1024 bytes).

Default: 1MB (1048576 bytes)

Validation

Configuration is automatically validated:

  • All timeouts must be positive
  • ReadTimeout should not exceed WriteTimeout
  • ShutdownTimeout must be at least 1 second
  • MaxHeaderBytes must be at least 1KB

Invalid configuration causes app.New() to return an error.

4 - Observability Options

Observability configuration options reference (metrics, tracing, logging).

Observability Options

These options are used with WithObservability():

app.WithObservability(
    app.WithLogging(logging.WithJSONHandler()),
    app.WithMetrics(),
    app.WithTracing(tracing.WithOTLP("localhost:4317")),
)

You can also configure observability using environment variables. See Environment Variables Guide for details.

Component Options

WithLogging

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

Enables structured logging with slog. Service name/version automatically injected.

Environment variable alternative:

export RIVAAS_LOG_LEVEL=info      # debug, info, warn, error
export RIVAAS_LOG_FORMAT=json     # json, text, console

WithMetrics

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

Enables metrics collection (Prometheus by default). Service name/version automatically injected.

Environment variable alternative:

export RIVAAS_METRICS_EXPORTER=prometheus  # or otlp, stdout
export RIVAAS_METRICS_ADDR=:9090          # Optional: custom Prometheus address
export RIVAAS_METRICS_PATH=/metrics        # Optional: custom Prometheus path

WithTracing

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

Enables distributed tracing. Service name/version automatically injected.

Environment variable alternative:

export RIVAAS_TRACING_EXPORTER=otlp        # or otlp-http, stdout
export RIVAAS_TRACING_ENDPOINT=localhost:4317  # Required for otlp/otlp-http

Metrics Server Options

WithMetricsOnMainRouter

func WithMetricsOnMainRouter(path string) ObservabilityOption

Mounts metrics endpoint on the main HTTP server (default: separate server).

WithMetricsSeparateServer

func WithMetricsSeparateServer(addr, path string) ObservabilityOption

Configures separate metrics server address and path.

Default: :9090/metrics

Path Filtering

WithExcludePaths

func WithExcludePaths(paths ...string) ObservabilityOption

Excludes exact paths from observability.

WithExcludePrefixes

func WithExcludePrefixes(prefixes ...string) ObservabilityOption

Excludes path prefixes from observability.

WithExcludePatterns

func WithExcludePatterns(patterns ...string) ObservabilityOption

Excludes paths matching regex patterns from observability.

WithoutDefaultExclusions

func WithoutDefaultExclusions() ObservabilityOption

Disables default path exclusions (/health*, /metrics, /debug/*).

Access Logging

WithAccessLogging

func WithAccessLogging(enabled bool) ObservabilityOption

Enables or disables access logging.

Default: true

WithAccessLogScope

func WithAccessLogScope(scope AccessLogScope) ObservabilityOption

Sets which requests are logged as access logs. Valid values are app.AccessLogScopeAll and app.AccessLogScopeErrorsOnly. Invalid values cause validation to fail at startup.

Scope values:

  • AccessLogScopeAll — Log every request (including 2xx). Use in production only if you need full request logs; consider log volume and cost.
  • AccessLogScopeErrorsOnly — Log only errors (status >= 400) and slow requests. Reduces log volume.

Access log scope and environment defaults

When you do not call WithAccessLogScope, the effective scope is determined by environment:

User choiceProductionDevelopment
NoneErrors-only (default)Full access logs (default)
WithAccessLogScope(AccessLogScopeErrorsOnly)Errors-onlyErrors-only
WithAccessLogScope(AccessLogScopeAll)Full access logsFull access logs

Slow requests are always logged regardless of scope. See WithSlowThreshold.

WithSlowThreshold

func WithSlowThreshold(d time.Duration) ObservabilityOption

Marks requests as slow if they exceed this duration.

Default: 1s

Example

app.WithObservability(
    // Components
    app.WithLogging(logging.WithJSONHandler()),
    app.WithMetrics(metrics.WithPrometheus(":9090", "/metrics")),
    app.WithTracing(tracing.WithOTLP("localhost:4317")),
    
    // Path filtering
    app.WithExcludePaths("/livez", "/readyz"),
    app.WithExcludePrefixes("/internal/"),
    
    // Access logging
    app.WithAccessLogScope(app.AccessLogScopeErrorsOnly),
    app.WithSlowThreshold(500 * time.Millisecond),
)

5 - Health Options

Health endpoint configuration options reference.

Health Options

These options are used with WithHealthEndpoints():

app.WithHealthEndpoints(
    app.WithReadinessCheck("database", dbCheck),
    app.WithHealthTimeout(800 * time.Millisecond),
)

Path Configuration

WithHealthPrefix

func WithHealthPrefix(prefix string) HealthOption

Mounts health endpoints under a prefix.

Default: "" (root)

WithLivezPath

func WithLivezPath(path string) HealthOption

Custom liveness probe path.

Default: "/livez"

WithReadyzPath

func WithReadyzPath(path string) HealthOption

Custom readiness probe path.

Default: "/readyz"

Check Configuration

WithHealthTimeout

func WithHealthTimeout(d time.Duration) HealthOption

Timeout for each health check.

Default: 1s

WithLivenessCheck

func WithLivenessCheck(name string, fn CheckFunc) HealthOption

Adds a liveness check. Liveness checks should be dependency-free and fast.

WithReadinessCheck

func WithReadinessCheck(name string, fn CheckFunc) HealthOption

Adds a readiness check. Readiness checks verify external dependencies.

CheckFunc

type CheckFunc func(context.Context) error

Health check function that returns nil if healthy, error if unhealthy.

Example

app.WithHealthEndpoints(
    app.WithHealthPrefix("/_system"),
    app.WithHealthTimeout(800 * time.Millisecond),
    app.WithLivenessCheck("process", func(ctx context.Context) error {
        return nil
    }),
    app.WithReadinessCheck("database", func(ctx context.Context) error {
        return db.PingContext(ctx)
    }),
)

// Endpoints:
// GET /_system/livez - Liveness (200 if all checks pass)
// GET /_system/readyz - Readiness (204 if all checks pass)

6 - Debug Options

Debug endpoint configuration options reference.

Debug Options

These options are used with WithDebugEndpoints():

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

Path Configuration

WithDebugPrefix

func WithDebugPrefix(prefix string) DebugOption

Mounts debug endpoints under a custom prefix.

Default: "/debug"

pprof Options

WithPprof

func WithPprof() DebugOption

Enables pprof endpoints unconditionally.

WithPprofIf

func WithPprofIf(condition bool) DebugOption

Conditionally enables pprof endpoints based on a boolean condition.

Available Endpoints

When pprof is 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
  • GET /debug/pprof/trace - Execution trace
  • GET /debug/pprof/allocs - Memory allocations
  • GET /debug/pprof/block - Block profile
  • GET /debug/pprof/goroutine - Goroutine profile
  • GET /debug/pprof/heap - Heap profile
  • GET /debug/pprof/mutex - Mutex profile
  • GET /debug/pprof/threadcreate - Thread creation profile

Security Warning

⚠️ Never enable pprof in production without proper authentication. Debug endpoints expose sensitive runtime information.

Example

// Development: enable unconditionally
app.WithDebugEndpoints(
    app.WithPprof(),
)

// Production: enable conditionally
app.WithDebugEndpoints(
    app.WithDebugPrefix("/_internal/debug"),
    app.WithPprofIf(os.Getenv("PPROF_ENABLED") == "true"),
)

7 - Context API

Context methods for request handling.

Request Binding

Bind

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

Binds request data and validates it. This is the main method for handling requests.

Reads data from all sources (path, query, headers, cookies, JSON, forms) based on struct tags. Then validates the data using the configured strategy.

Returns: Error if binding or validation fails.

MustBind

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

Binds and validates, automatically sending error responses on failure.

Use this when you want simple error handling. If binding or validation fails, it sends the error response and returns false.

Returns: True if successful, false if error was sent.

BindOnly

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

Binds request data without validation.

Use this when you need to process data before validating it.

Returns: Error if binding fails.

Validate

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

Validates a struct using the configured strategy.

Use this after BindOnly() when you need fine-grained control.

Returns: Validation error if validation fails.

Error Handling

All error handling methods automatically format the error response and abort the handler chain. No further handlers will run after calling these methods.

Fail

func (c *Context) Fail(err error)

Sends a formatted error response using the configured formatter. The HTTP status code is determined from the error (if it implements HTTPStatus() int) or defaults to 500.

Parameters:

  • err: The error to send. If nil, the method returns without doing anything.

Behavior:

  • Formats the error using content negotiation
  • Writes the HTTP response
  • Aborts the handler chain

FailStatus

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

Sends an error response with an explicit HTTP status code.

Parameters:

  • status: The HTTP status code to use
  • err: The error to send

Behavior:

  • Wraps the error with the specified status code
  • Formats and sends the response
  • Aborts the handler chain

NotFound

func (c *Context) NotFound(err error)

Sends a 404 Not Found error response.

Parameters:

  • err: The error to send, or nil for a generic “Not Found” message

BadRequest

func (c *Context) BadRequest(err error)

Sends a 400 Bad Request error response.

Parameters:

  • err: The error to send, or nil for a generic “Bad Request” message

Unauthorized

func (c *Context) Unauthorized(err error)

Sends a 401 Unauthorized error response.

Parameters:

  • err: The error to send, or nil for a generic “Unauthorized” message

Forbidden

func (c *Context) Forbidden(err error)

Sends a 403 Forbidden error response.

Parameters:

  • err: The error to send, or nil for a generic “Forbidden” message

Conflict

func (c *Context) Conflict(err error)

Sends a 409 Conflict error response.

Parameters:

  • err: The error to send, or nil for a generic “Conflict” message

Gone

func (c *Context) Gone(err error)

Sends a 410 Gone error response.

Parameters:

  • err: The error to send, or nil for a generic “Gone” message

UnprocessableEntity

func (c *Context) UnprocessableEntity(err error)

Sends a 422 Unprocessable Entity error response.

Parameters:

  • err: The error to send, or nil for a generic “Unprocessable Entity” message

TooManyRequests

func (c *Context) TooManyRequests(err error)

Sends a 429 Too Many Requests error response.

Parameters:

  • err: The error to send, or nil for a generic “Too Many Requests” message

InternalError

func (c *Context) InternalError(err error)

Sends a 500 Internal Server Error response.

Parameters:

  • err: The error to send, or nil for a generic “Internal Server Error” message

ServiceUnavailable

func (c *Context) ServiceUnavailable(err error)

Sends a 503 Service Unavailable error response.

Parameters:

  • err: The error to send, or nil for a generic “Service Unavailable” message

Logging

To log from a handler with trace correlation, pass the request context to the standard library’s context-aware logging functions. For example: slog.InfoContext(c.RequestContext(), "msg", ...) or slog.ErrorContext(c.RequestContext(), "msg", ...). When the app is configured with observability (logging and tracing), trace_id and span_id are injected automatically from the active OpenTelemetry span.

Presence

Presence

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

Returns the presence map for the current request (tracks which fields were present in JSON).

ResetBinding

func (c *Context) ResetBinding()

Resets binding metadata (useful for testing).

Router Context

The app Context embeds router.Context, providing access to all router features:

  • c.Request - HTTP request
  • c.Response - HTTP response writer
  • c.Param(name) - Path parameter
  • c.Query(name) - Query parameter
  • c.JSON(status, data) - Send JSON response
  • c.String(status, text) - Send text response
  • c.HTML(status, html) - Send HTML response
  • And more…

See Router Context API for complete router context reference.

8 - Lifecycle Hooks

Lifecycle hook APIs and execution order.

Hook Methods

All hook registration methods return an error when called after the router is frozen (e.g. after Start() or Router().Freeze()). Register all hooks before starting the server. Use errors.Is(err, app.ErrRouterFrozen) to detect this case.

OnStart

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

Called before server starts. Hooks run sequentially and stop on first error.

Use for: Database connections, migrations, initialization that must succeed.

OnReady

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

Called after server starts listening. Hooks run asynchronously and don’t block startup.

Use for: Warmup tasks, service discovery registration.

OnShutdown

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

Called during graceful shutdown. Hooks run in LIFO order with shutdown timeout.

Use for: Closing connections, flushing buffers, cleanup that must complete within timeout.

OnStop

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

Called after shutdown completes. Hooks run in best-effort mode and panics are caught.

Use for: Final cleanup that doesn’t need timeout.

OnRoute

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

Called when a route is registered. Disabled after router freeze.

Use for: Route validation, logging, documentation generation.

OnReload

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

Called when the application receives a reload signal (SIGHUP) or when Reload() is called programmatically. SIGHUP signal handling is automatically enabled when you register this hook.

If no OnReload hooks are registered, SIGHUP is ignored on Unix so the process keeps running (e.g. kill -HUP does not terminate it).

Hooks run sequentially and stop on first error. Errors are logged but don’t crash the server.

Use for: Reloading configuration, rotating certificates, flushing caches, updating runtime settings.

Platform: SIGHUP works on Unix/Linux/macOS. On Windows, use programmatic Reload().

Reload

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

Manually triggers all registered OnReload hooks. Useful for admin endpoints or Windows where SIGHUP isn’t available.

Returns an error if any hook fails, but the server continues running with the old configuration.

Post-freeze registration

Registering any lifecycle hook after the router is frozen (e.g. after Start() or Router().Freeze()) returns an error instead of panicking. Register all hooks before starting the server. Use errors.Is(err, app.ErrRouterFrozen) to detect this case programmatically.

Execution Flow

1. app.Start(ctx) called
2. OnStart hooks execute (sequential, stop on error)
3. Server starts listening
4. OnReady hooks execute (async, non-blocking)
5. Server handles requests...
   → OnReload hooks execute when SIGHUP received (sequential, logged on error)
6. Context canceled (SIGTERM/SIGINT)
7. OnShutdown hooks execute (LIFO order, with timeout)
8. Server shutdown complete
9. OnStop hooks execute (best-effort, no timeout)
10. Process exits

Hook Characteristics

HookOrderError HandlingTimeoutAsync
OnStartSequentialStop on first errorNoNo
OnReady-Panic caught and loggedNoYes
OnReloadSequentialStop on first error, loggedNoNo
OnShutdownLIFOErrors ignoredYes (shutdown timeout)No
OnStop-Panic caught and loggedNoNo
OnRouteSequential-NoNo

Example

a := app.MustNew()

// OnStart: Initialize (sequential, stops on error)
if err := a.OnStart(func(ctx context.Context) error {
    return db.Connect(ctx)
}); err != nil {
    log.Fatal(err)
}

// OnReady: Post-startup (async, non-blocking)
if err := a.OnReady(func() {
    consul.Register("my-service", ":8080")
}); err != nil {
    log.Fatal(err)
}

// OnReload: Reload configuration (sequential, logged on error)
if err := a.OnReload(func(ctx context.Context) error {
    cfg, err := loadConfig("config.yaml")
    if err != nil {
        return err
    }
    applyConfig(cfg)
    return nil
}); err != nil {
    log.Fatal(err)
}

// OnShutdown: Graceful cleanup (LIFO, with timeout)
if err := a.OnShutdown(func(ctx context.Context) {
    db.Close()
}); err != nil {
    log.Fatal(err)
}

// OnStop: Final cleanup (best-effort)
if err := a.OnStop(func() {
    cleanupTempFiles()
}); err != nil {
    log.Fatal(err)
}

9 - Troubleshooting

Common issues and solutions for the App package.

Configuration Errors

Validation Errors

Problem: app.New() returns validation errors.

Solution: Check error message for specific field. Common issues:

  • Empty service name or version.
  • Invalid environment. Must be “development” or “production”.
  • ReadTimeout greater than WriteTimeout.
  • ShutdownTimeout less than 1 second.
  • MaxHeaderBytes less than 1KB.

Example:

a, err := app.New(
    app.WithServiceName(""),  // ❌ Empty
)
// Error: "serviceName must not be empty"

Import Errors

Problem: Cannot import rivaas.dev/app.

Solution:

go get rivaas.dev/app
go mod tidy

Ensure Go 1.25+ is installed.

Server Issues

Port Already in Use

Problem: Server fails to start with “address already in use”.

Solution: Check if port is in use (default is 8080 for HTTP, 8443 for TLS/mTLS):

lsof -i :8080
# Or for TLS/mTLS
lsof -i :8443
# Or
netstat -an | grep 8080

Kill the process or use a different port with WithPort(n).

Routes Not Registering

Problem: Routes return 404 even though registered.

Solution:

  • Ensure routes registered before Start().
  • Check paths match exactly. They are case-sensitive.
  • Verify HTTP method matches.
  • Router freezes on startup. Can’t add routes after.
  • Lifecycle hook registration (OnStart, OnReady, OnShutdown, etc.) after freeze returns an error instead of panicking. Check and handle the error (e.g. in main) and register all hooks before Start().

Unsupported HTTP Method Panic

Problem: Panic with message like unsupported HTTP method "…" or supported: GET, POST, ....

Solution: Use only the provided method shortcuts: a.GET, a.POST, a.PUT, a.DELETE, a.PATCH, a.HEAD, a.OPTIONS, and the same on Group and VersionGroup. If the panic appears in tests or custom code that passes a method string, ensure that string is one of: GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS.

Graceful Shutdown Not Working

Problem: Server doesn’t shut down cleanly.

Solution:

  • Increase shutdown timeout: WithShutdownTimeout(60 * time.Second).
  • Check OnShutdown hooks complete quickly.
  • Verify handlers respect context cancellation.

Observability Issues

Metrics Not Appearing

Problem: Metrics endpoint returns 404.

Solution:

  • Ensure metrics enabled: WithMetrics()
  • Check metrics address: a.GetMetricsServerAddress()
  • Default is separate server on :9090/metrics
  • Use WithMetricsOnMainRouter("/metrics") to mount on main router

Tracing Not Working

Problem: No traces appear in backend.

Solution:

  • Verify tracing enabled: WithTracing()
  • Check OTLP endpoint configuration
  • Ensure tracing backend is running and accessible
  • Verify network connectivity
  • Check logs for tracing initialization errors

Logs Not Appearing

Problem: No logs are written.

Solution:

  • Ensure logging enabled: WithLogging()
  • Check log level configuration
  • Verify logger handler is correct (JSON, Console, etc.)
  • Use c.Logger() in handlers, not package-level logger

Middleware Issues

Middleware Not Executing

Problem: Middleware functions aren’t being called.

Solution:

  • Ensure middleware added before routes
  • Check middleware calls c.Next()
  • Verify middleware isn’t returning early
  • Default recovery middleware is included automatically

Authentication Failing

Problem: Auth middleware not working correctly.

Solution:

  • Check header/token extraction logic
  • Verify middleware order (auth should run early)
  • Ensure c.Next() is called on success
  • Test middleware in isolation

Testing Issues

Test Hangs

Problem: a.Test() never returns.

Solution:

  • Set timeout: a.Test(req, app.WithTimeout(5*time.Second))
  • Check for infinite loops in handler
  • Verify middleware calls c.Next()

Test Fails with Panic

Problem: Test panics instead of returning error.

Solution:

  • Use recover() in test or
  • Check that handler doesn’t panic
  • Recovery middleware catches panics in real server

Health Check Issues

Health Checks Always Failing

Problem: /livez or /readyz always returns 503.

Solution:

  • Check health check functions return nil on success
  • Verify dependencies (database, cache) are accessible
  • Check health timeout is sufficient
  • Test health checks independently

Health Checks Never Complete

Problem: Health checks timeout.

Solution:

  • Increase timeout: WithHealthTimeout(2 * time.Second)
  • Check dependencies respond within timeout
  • Verify no deadlocks in check functions
  • Use context timeout in check functions

Debugging Tips

Enable Development Mode

app.WithEnvironment("development")

Enables verbose logging and route table display.

Check Observability Status

if a.Metrics() != nil {
    fmt.Println("Metrics:", a.GetMetricsServerAddress())
}
if a.Tracing() != nil {
    fmt.Println("Tracing enabled")
}

Use Test Helpers

resp, err := a.Test(req)  // Test without starting server

Enable GC Tracing

GODEBUG=gctrace=1 go run main.go

Getting Help