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
- 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
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 instanceerror - 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:
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.
func WithErrorFormatter(formatter errors.Formatter) Option
Configures a single error formatter for all error responses.
func WithErrorFormatters(formatters map[string]errors.Formatter) Option
Configures multiple error formatters with content negotiation based on Accept header.
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
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
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 choice | Production | Development |
|---|
| None | Errors-only (default) | Full access logs (default) |
WithAccessLogScope(AccessLogScopeErrorsOnly) | Errors-only | Errors-only |
WithAccessLogScope(AccessLogScopeAll) | Full access logs | Full 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 indexGET /debug/pprof/cmdline - Command lineGET /debug/pprof/profile - CPU profileGET /debug/pprof/symbol - Symbol lookupGET /debug/pprof/trace - Execution traceGET /debug/pprof/allocs - Memory allocationsGET /debug/pprof/block - Block profileGET /debug/pprof/goroutine - Goroutine profileGET /debug/pprof/heap - Heap profileGET /debug/pprof/mutex - Mutex profileGET /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 useerr: 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 requestc.Response - HTTP response writerc.Param(name) - Path parameterc.Query(name) - Query parameterc.JSON(status, data) - Send JSON responsec.String(status, text) - Send text responsec.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
| Hook | Order | Error Handling | Timeout | Async |
|---|
| OnStart | Sequential | Stop on first error | No | No |
| OnReady | - | Panic caught and logged | No | Yes |
| OnReload | Sequential | Stop on first error, logged | No | No |
| OnShutdown | LIFO | Errors ignored | Yes (shutdown timeout) | No |
| OnStop | - | Panic caught and logged | No | No |
| OnRoute | Sequential | - | No | No |
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