Getting Started
Start using Rivaas in minutes
Learn how to install Rivaas and build your first web application. This guide shows you how to create a running API with production-ready features.
What You’ll Learn
This section shows you how to:
- ✅ Install and verify Rivaas
- ✅ Build a complete REST API with multiple routes
- ✅ Configure your application for different environments
- ✅ Add middleware for CORS, authentication, and more
- ✅ Test your application
- ✅ Deploy with confidence
Prerequisites
Before you begin, you need:
- Go 1.25 or higher installed (Download Go)
- Basic Go programming knowledge
- Basic HTTP and REST API knowledge
Check your Go installation:
go version
# Should output: go version go1.25.x ...
Learning Path
Follow these steps in order:
Install the Rivaas framework and verify your setup.
What you’ll do:
- Install the
app package - Verify installation with a test program
- Troubleshoot common issues
Build a complete Hello World API with multiple endpoints.
What you’ll do:
- Create a new project
- Define routes and handlers
- Handle JSON requests and responses
- Set up graceful shutdown
- Test your API
Learn essential configuration options for your application.
What you’ll do:
- Set service metadata
- Configure health endpoints
- Enable observability (logging, metrics, tracing)
- Set up environment-specific configuration
- Understand server timeouts
Add functionality that works across all routes.
What you’ll do:
- Learn middleware concepts
- Use built-in middleware (CORS, request ID, auth)
- Create custom middleware
- Apply middleware globally or to specific routes
- Learn execution order
Continue your journey beyond the basics.
What you’ll explore:
- Production deployment
- Advanced routing patterns
- Testing strategies
- Example applications
Quick Start
Want to skip ahead? Here’s the minimal setup:
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!",
})
})
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer cancel()
if err := a.Start(ctx); err != nil {
log.Fatal(err)
}
}
This creates a basic API server. Continue through the guide to learn about configuration, middleware, and production practices.
Need Help?
Ready?
Start with Installation →
1 - Installation
Install Rivaas and verify your setup
Installing Rivaas is easy. Use it as a complete framework with the app package or use individual packages as needed.
Install the Full Framework
The app package provides a complete web framework. It includes everything you need to build production-ready APIs:
This installs the main framework with all packages (router, logging, metrics, tracing, etc.).
Install Individual Packages
Rivaas packages work independently. Install only what you need:
# High-performance router
go get rivaas.dev/router
# Full framework
go get rivaas.dev/app
# Request binding
go get rivaas.dev/binding
# Validation
go get rivaas.dev/validation
# Configuration
go get rivaas.dev/config
# Structured logging
go get rivaas.dev/logging
# Metrics collection
go get rivaas.dev/metrics
# Distributed tracing
go get rivaas.dev/tracing
# OpenAPI generation
go get rivaas.dev/openapi
# Error formatting
go get rivaas.dev/errors
Verify Installation
Create a simple test file to verify your installation:
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"rivaas.dev/app"
)
func main() {
a, err := app.New()
if err != nil {
log.Fatalf("Failed to create app: %v", err)
}
a.GET("/", func(c *app.Context) {
c.JSON(http.StatusOK, map[string]string{
"message": "✅ Rivaas installed successfully!",
})
})
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer cancel()
log.Println("Test server running on http://localhost:8080")
if err := a.Start(ctx); err != nil {
log.Fatal(err)
}
}
Run it:
Test it in another terminal:
curl http://localhost:8080
# Output: {"message":"✅ Rivaas installed successfully!"}
Press Ctrl+C to stop the server gracefully.
System Requirements
- Go Version: 1.25 or higher
- Operating Systems: Linux, macOS, Windows
- Architecture: amd64, arm64
Updating Rivaas
To update to the latest version:
To update a specific package:
go get -u rivaas.dev/router
Development Dependencies
For development, you may want additional tools:
# Install Go tools (optional but recommended)
go install golang.org/x/tools/cmd/goimports@latest
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
Troubleshooting
Go Version Issues
If you see an error about Go version:
go: module rivaas.dev/app requires go >= 1.25
Update your Go installation to 1.25 or higher from go.dev/dl.
Module Cache Issues
If installation fails, try cleaning the module cache:
go clean -modcache
go get rivaas.dev/app
Network Issues
If you’re behind a proxy or firewall:
# Set proxy (if needed)
export GOPROXY=https://proxy.golang.org,direct
# Or use a custom proxy
export GOPROXY=https://goproxy.io,direct
Next Steps
Now that you have Rivaas installed, build your first application:
Build Your First Application →
2 - Your First Application
Build a complete REST API quickly
Build a simple REST API to learn Rivaas basics. You’ll create a working application with multiple routes, JSON responses, and graceful shutdown.
Create Your Project
Create a new directory and initialize a Go module:
mkdir hello-rivaas
cd hello-rivaas
go mod init example.com/hello-rivaas
Install Rivaas
Write Your Application
Create a file named main.go:
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"rivaas.dev/app"
)
func main() {
// Create a new Rivaas application
a := app.MustNew(
app.WithServiceName("hello-rivaas"),
app.WithServiceVersion("v1.0.0"),
)
// Define routes
a.GET("/", handleRoot)
a.GET("/hello/:name", handleHello)
a.POST("/echo", handleEcho)
// Setup graceful shutdown
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer cancel()
// Start the server
log.Println("🚀 Starting server on http://localhost:8080")
if err := a.Start(ctx); err != nil {
log.Fatal(err)
}
}
// handleRoot returns a welcome message
func handleRoot(c *app.Context) {
c.JSON(http.StatusOK, map[string]string{
"message": "Welcome to Rivaas!",
"version": "v1.0.0",
})
}
// handleHello greets a user by name
func handleHello(c *app.Context) {
name := c.Param("name")
c.JSON(http.StatusOK, map[string]string{
"message": "Hello, " + name + "!",
})
}
// handleEcho echoes back the request body
func handleEcho(c *app.Context) {
var body map[string]any
if err := c.Bind(&body); err != nil {
c.JSON(http.StatusBadRequest, map[string]string{
"error": "Invalid JSON",
})
return
}
c.JSON(http.StatusOK, map[string]any{
"echo": body,
})
}
Run Your Application
Start the server:
You should see output like:
🚀 Starting server on http://localhost:8080
Test Your API
Open a new terminal and test the endpoints:
Test the root endpoint
curl http://localhost:8080/
Response:
{
"message": "Welcome to Rivaas!",
"version": "v1.0.0"
}
Test the greeting endpoint
curl http://localhost:8080/hello/World
Response:
{
"message": "Hello, World!"
}
Test the echo endpoint
curl -X POST http://localhost:8080/echo \
-H "Content-Type: application/json" \
-d '{"name": "Rivaas", "type": "framework"}'
Response:
{
"echo": {
"name": "Rivaas",
"type": "framework"
}
}
Understanding the Code
Here’s what each part does:
1. Creating the Application
a := app.MustNew(
app.WithServiceName("hello-rivaas"),
app.WithServiceVersion("v1.0.0"),
)
MustNew() creates a new application. Panics on error. Use in main() functions.WithServiceName() sets the service name.WithServiceVersion() sets the version.
2. Defining Routes
a.GET("/", handleRoot)
a.GET("/hello/:name", handleHello)
a.POST("/echo", handleEcho)
GET() and POST() register route handlers.:name is a path parameter. Access it with c.Param("name").- Handler functions receive an
*app.Context with all request data.
3. Graceful Shutdown
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer cancel()
if err := a.Start(ctx); err != nil {
log.Fatal(err)
}
signal.NotifyContext() creates a context that cancels on SIGINT (Ctrl+C) or SIGTERM.Start() starts the server and blocks until the context is canceled.- The server shuts down gracefully. It finishes active requests before stopping.
4. Handler Functions
func handleRoot(c *app.Context) {
c.JSON(http.StatusOK, map[string]string{
"message": "Welcome to Rivaas!",
})
}
- Handlers receive an
*app.Context. c.JSON() sends a JSON response.c.Param() gets path parameters.c.Bind() parses request bodies. It auto-detects JSON, form, and other formats.
Common Patterns
Path Parameters
// Route: /users/:id/posts/:postId
a.GET("/users/:id/posts/:postId", func(c *app.Context) {
userID := c.Param("id")
postID := c.Param("postId")
c.JSON(http.StatusOK, map[string]string{
"user_id": userID,
"post_id": postID,
})
})
Query Parameters
// Route: /search?q=rivaas&limit=10
a.GET("/search", func(c *app.Context) {
query := c.Query("q")
limit := c.QueryDefault("limit", "20")
c.JSON(http.StatusOK, map[string]string{
"query": query,
"limit": limit,
})
})
a.GET("/headers", func(c *app.Context) {
userAgent := c.Request.Header.Get("User-Agent")
c.JSON(http.StatusOK, map[string]string{
"user_agent": userAgent,
})
})
Different Status Codes
a.GET("/not-found", func(c *app.Context) {
c.JSON(http.StatusNotFound, map[string]string{
"error": "Resource not found",
})
})
a.POST("/created", func(c *app.Context) {
c.JSON(http.StatusCreated, map[string]string{
"message": "Resource created",
})
})
Testing Your Application
Rivaas provides testing utilities for integration tests:
package main
import (
"net/http"
"net/http/httptest"
"testing"
"rivaas.dev/app"
)
func TestHelloEndpoint(t *testing.T) {
// Create test app
a, err := app.New()
if err != nil {
t.Fatalf("Failed to create app: %v", err)
}
a.GET("/hello/:name", handleHello)
// Create test request
req := httptest.NewRequest(http.MethodGet, "/hello/Gopher", nil)
// Test the request
resp, err := a.Test(req)
if err != nil {
t.Fatalf("Request failed: %v", err)
}
defer resp.Body.Close()
// Check status code
if resp.StatusCode != http.StatusOK {
t.Errorf("Expected status 200, got %d", resp.StatusCode)
}
}
Key Testing Methods:
a.Test(req) - Execute a request without starting the servera.TestJSON(method, path, body) - Test JSON endpointsapp.ExpectJSON(t, resp, status, target) - Verify JSON responses
See the blog example for comprehensive testing patterns.
Common Mistakes
Forgetting Error Handling
// ❌ Bad: Ignoring errors
a := app.MustNew() // Panics on error
// ✅ Good: Handle errors properly
a, err := app.New()
if err != nil {
log.Fatalf("Failed to create app: %v", err)
}
Not Using Context for Shutdown
// ❌ Bad: No graceful shutdown
a.Start(context.Background(), ":8080")
// ✅ Good: Graceful shutdown with signals
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer cancel()
a.Start(ctx)
Registering Routes After Start
// ❌ Bad: Routes registered after Start
a.Start(ctx)
a.GET("/late", handler) // Won't work!
// ✅ Good: Routes before Start
a.GET("/early", handler)
a.Start(ctx)
Production Basics
Before deploying your first application:
- ✅ Use environment-based configuration (see Configuration)
- ✅ Add health endpoints for Kubernetes/Docker
- ✅ Enable structured logging
- ✅ Set appropriate timeouts
- ✅ Add recovery middleware (included by default)
Quick Production Setup:
a, err := app.New(
app.WithServiceName("my-api"),
app.WithServiceVersion("v1.0.0"),
app.WithEnvironment("production"),
app.WithHealthEndpoints(
app.WithReadinessCheck("ready", func(ctx context.Context) error {
return nil // Add real checks here
}),
),
)
See the full-featured example for production patterns.
What’s Next?
You now have a working Rivaas application. Here are the next steps:
Complete Example
The complete code is available in the examples repository.
Troubleshooting
Port Already in Use
If you see “address already in use”:
# Find what's using port 8080
lsof -i :8080
# Kill the process or use a different port
Change the port when creating the app:
a, err := app.New(
app.WithServiceName("my-api"),
app.WithPort(3000), // Use port 3000 instead of default 8080
)
// ...
a.Start(ctx)
JSON Binding Errors
If Bind() fails for JSON requests, ensure:
- Content-Type header is set to
application/json - Request body contains valid JSON
- JSON structure matches your Go struct
Ready to learn more? Continue to Configuration →
3 - Configuration
Configure your Rivaas application
Overview
Rivaas uses the functional options pattern for configuration. This provides a clean, self-documenting API. It’s backward-compatible. This guide covers basic configuration options.
💡 First Time? Focus on sections marked with ⭐. Skip advanced topics for now.
Configuration Philosophy
- Sensible Defaults: Works out of the box.
- Progressive Disclosure: Start simple. Add complexity as needed.
- Type Safety: Configuration errors are caught at startup.
- Environment Aware: Different defaults for dev and prod.
⭐ Basic Configuration
Set your service name and version:
a := app.MustNew(
app.WithServiceName("my-api"),
app.WithServiceVersion("v1.2.3"),
app.WithEnvironment("production"),
)
These values are sent to all observability components. This includes logging, metrics, and tracing.
Advanced: Server Configuration
⚠️ Advanced Topic: Most applications don’t need custom server configuration. The defaults work for production.
Timeouts
Configure server timeouts to protect against slow clients:
a := app.MustNew(
app.WithServiceName("my-api"),
app.WithServerConfig(
app.WithReadTimeout(10 * time.Second),
app.WithWriteTimeout(15 * time.Second),
app.WithIdleTimeout(60 * time.Second),
app.WithShutdownTimeout(30 * time.Second),
),
)
Timeout Options:
| Option | Description | Default |
|---|
WithReadTimeout | Maximum time to read request | 10s |
WithWriteTimeout | Maximum time to write response | 10s |
WithIdleTimeout | Maximum idle connection time | 60s |
WithReadHeaderTimeout | Maximum time to read headers | 2s |
WithShutdownTimeout | Graceful shutdown timeout | 30s |
Request Limits
Configure request size limits:
app.WithServerConfig(
app.WithMaxHeaderBytes(1 << 20), // 1MB
)
⭐ Environment Modes
Rivaas supports two environment modes with different defaults:
Development Mode (Default)
a := app.MustNew(
app.WithEnvironment("development"),
)
Features:
- Verbose logging enabled
- Access logging for all requests
- Development-friendly error messages
- Pretty-printed JSON logs
Production Mode
a := app.MustNew(
app.WithEnvironment("production"),
)
Features:
- Error-only logging
- JSON structured logs
- Minimal overhead
- Production-ready defaults
Observability
💡 Note: This section covers observability setup. For detailed observability patterns, see the Observability Guide.
Enable logging, metrics, and tracing:
import (
"rivaas.dev/app"
"rivaas.dev/logging"
"rivaas.dev/metrics"
"rivaas.dev/tracing"
)
a := app.MustNew(
app.WithServiceName("my-api"),
app.WithObservability(
app.WithLogging(logging.WithJSONHandler()),
app.WithMetrics(),
app.WithTracing(tracing.WithStdout()),
),
)
Logging Options
app.WithLogging(
logging.WithJSONHandler(), // JSON output
logging.WithLevel(logging.LevelInfo), // Log level
)
// Or use console handler (development)
app.WithLogging(logging.WithConsoleHandler())
Metrics Options
// Prometheus metrics (default)
app.WithMetrics()
// Custom Prometheus endpoint
app.WithMetrics(
metrics.WithPrometheus(":9090", "/metrics"),
)
// OTLP metrics
app.WithMetrics(
metrics.WithOTLP("localhost:4317"),
)
Tracing Options
// Stdout tracing (development)
app.WithTracing(tracing.WithStdout())
// OTLP tracing (production)
app.WithTracing(
tracing.WithOTLP("jaeger:4317"),
tracing.WithSampleRate(0.1), // Sample 10% of requests
)
Exclude Paths from Observability
Exclude health checks and static paths from logging/metrics/tracing:
app.WithObservability(
app.WithLogging(),
app.WithMetrics(),
app.WithTracing(),
app.WithExcludePaths("/livez", "/readyz", "/metrics"),
app.WithExcludePrefixes("/static", "/assets"),
)
⭐ Health Endpoints
Add Kubernetes-compatible health endpoints:
a := app.MustNew(
app.WithServiceName("my-api"),
app.WithHealthEndpoints(
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) // Check DB connection
}),
),
)
This registers:
GET /livez — Liveness probeGET /readyz — Readiness probe
Custom Health Paths
app.WithHealthEndpoints(
app.WithHealthPrefix("/_system"), // Prefix: /_system/livez
app.WithLivezPath("/live"), // Custom path: /_system/live
app.WithReadyzPath("/ready"), // Custom path: /_system/ready
app.WithHealthTimeout(500 * time.Millisecond),
)
Advanced: Debug Endpoints
⚠️ Security Critical: Only enable pprof in controlled environments.
Enable pprof for profiling (use with caution):
// Enable conditionally (recommended for production)
app.WithDebugEndpoints(
app.WithPprofIf(os.Getenv("PPROF_ENABLED") == "true"),
)
// Always enable (development only)
app.WithDebugEndpoints(
app.WithPprof(),
)
⚠️ Security Warning: Never expose pprof endpoints in production without proper authentication.
Advanced: Middleware Configuration
Add middleware during initialization or after app creation:
import (
"rivaas.dev/router/middleware/cors"
"rivaas.dev/router/middleware/requestid"
)
a := app.MustNew(
app.WithServiceName("my-api"),
)
// Add middleware after creation
a.Use(requestid.New())
a.Use(cors.New(
cors.WithAllowedOrigins([]string{"https://example.com"}),
))
Or during initialization:
a := app.MustNew(
app.WithServiceName("my-api"),
app.WithMiddleware(
requestid.New(),
cors.New(cors.WithAllowAllOrigins(true)),
),
)
💡 Learn More: See the Middleware Guide for detailed middleware usage patterns.
Complete Example
Here’s a production-ready configuration:
package main
import (
"context"
"log"
"os"
"time"
"rivaas.dev/app"
"rivaas.dev/logging"
"rivaas.dev/metrics"
"rivaas.dev/tracing"
"rivaas.dev/router/middleware/cors"
"rivaas.dev/router/middleware/requestid"
)
func main() {
a := app.MustNew(
// Service metadata
app.WithServiceName("my-api"),
app.WithServiceVersion("v1.0.0"),
app.WithEnvironment("production"),
// Server configuration
app.WithServerConfig(
app.WithReadTimeout(10 * time.Second),
app.WithWriteTimeout(15 * time.Second),
app.WithShutdownTimeout(30 * time.Second),
),
// Observability
app.WithObservability(
app.WithLogging(logging.WithJSONHandler()),
app.WithMetrics(metrics.WithPrometheus(":9090", "/metrics")),
app.WithTracing(tracing.WithOTLP("jaeger:4317")),
app.WithExcludePaths("/livez", "/readyz", "/metrics"),
),
// Health checks
app.WithHealthEndpoints(
app.WithReadinessCheck("database", checkDatabase),
),
// Debug (conditional)
app.WithDebugEndpoints(
app.WithPprofIf(os.Getenv("PPROF_ENABLED") == "true"),
),
)
// Add middleware
a.Use(requestid.New())
a.Use(cors.New(cors.WithAllowedOrigins([]string{
"https://example.com",
})))
// Register routes
a.GET("/", handleRoot)
// Start server
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
defer cancel()
if err := a.Start(ctx); err != nil {
log.Fatal(err)
}
}
func checkDatabase(ctx context.Context) error {
// Implement your database check
return nil
}
func handleRoot(c *app.Context) {
c.JSON(200, map[string]string{"status": "ok"})
}
Advanced: Configuration Validation
Rivaas validates configuration at startup and returns clear errors:
a, err := app.New(
app.WithServerConfig(
app.WithReadTimeout(15 * time.Second),
app.WithWriteTimeout(10 * time.Second), // ❌ Read > Write
),
)
if err != nil {
// Error: "server.readTimeout: read timeout should not exceed write timeout"
}
Advanced: Environment Variables
While Rivaas doesn’t directly use environment variables, you can easily integrate them:
import "os"
func getEnv(key, fallback string) string {
if value := os.Getenv(key); value != "" {
return value
}
return fallback
}
a := app.MustNew(
app.WithServiceName(getEnv("SERVICE_NAME", "my-api")),
app.WithServiceVersion(getEnv("SERVICE_VERSION", "v1.0.0")),
app.WithEnvironment(getEnv("ENVIRONMENT", "development")),
)
Next Steps
Now that you understand configuration, explore these topics:
Reference
For a complete list of all configuration options, see the App Options Reference.
4 - Using Middleware
Add functionality to your application with middleware
Middleware functions intercept HTTP requests. They add functionality like logging, authentication, and error recovery to your Rivaas application.
What is Middleware?
Middleware wraps your route handlers. It runs code before and after the handler. Think of it as layers around your core logic:
Request → Middleware 1 → Middleware 2 → Handler → Middleware 2 → Middleware 1 → Response
Common uses:
- Log requests and responses
- Authenticate and authorize users
- Recover from errors
- Modify requests and responses
- Limit request rates
- Add CORS headers
Built-in Middleware
Rivaas includes 12 production-ready middleware packages:
| Middleware | Purpose | Production-Ready |
|---|
recovery | Panic recovery | ✅ Auto-included |
requestid | Request ID tracking | ✅ |
cors | Cross-Origin Resource Sharing | ✅ |
timeout | Request timeouts | ✅ |
accesslog | Access logging | ✅ |
ratelimit | Rate limiting | ⚠️ Single-instance only |
basicauth | HTTP Basic Auth | ✅ |
bodylimit | Request size limits | ✅ |
compression | Response compression | ✅ |
security | Security headers | ✅ |
methodoverride | HTTP method override | ✅ |
trailingslash | Trailing slash handling | ✅ |
Check the Middleware Reference for complete documentation.
Adding Middleware
Global Middleware
Apply middleware to all routes:
import (
"rivaas.dev/app"
"rivaas.dev/router/middleware/requestid"
"rivaas.dev/router/middleware/cors"
)
func main() {
a, err := app.New()
if err != nil {
log.Fatal(err)
}
// Add middleware before registering routes
a.Use(requestid.New())
a.Use(cors.New(cors.WithAllowAllOrigins(true)))
// Register routes
a.GET("/", handleRoot)
// Start server
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer cancel()
a.Start(ctx)
}
Group Middleware
Apply middleware to specific route groups:
// Public routes - no auth
a.GET("/", handlePublic)
// API routes - with auth
api := a.Group("/api", authMiddleware)
api.GET("/users", getUsers)
api.POST("/users", createUser)
// Admin routes - with admin auth
admin := a.Group("/admin", authMiddleware, adminMiddleware)
admin.GET("/dashboard", getDashboard)
Route-Specific Middleware
Apply middleware to individual routes:
a.GET("/public", publicHandler)
a.GET("/protected", protectedHandler, authMiddleware)
Common Middleware Patterns
Request ID
Track requests across distributed systems with unique, time-ordered IDs:
import "rivaas.dev/router/middleware/requestid"
// UUID v7 by default (36 chars, time-ordered, RFC 9562)
a.Use(requestid.New())
// Or use ULID for shorter IDs (26 chars)
a.Use(requestid.New(requestid.WithULID()))
// In your handler
a.GET("/", func(c *app.Context) {
reqID := c.Response.Header().Get("X-Request-ID")
c.JSON(http.StatusOK, map[string]string{
"request_id": reqID,
})
})
Test it:
curl -i http://localhost:8080/
# X-Request-ID: 018f3e9a-1b2c-7def-8000-abcdef123456 (UUID v7)
Both UUID v7 and ULID are lexicographically sortable, making them ideal for debugging and log correlation.
CORS
Enable cross-origin requests:
import "rivaas.dev/router/middleware/cors"
// Development: Allow all origins
a.Use(cors.New(cors.WithAllowAllOrigins(true)))
// Production: Specific origins
a.Use(cors.New(
cors.WithAllowedOrigins([]string{
"https://example.com",
"https://app.example.com",
}),
cors.WithAllowedMethods([]string{"GET", "POST", "PUT", "DELETE"}),
cors.WithAllowCredentials(true),
))
Timeout
Prevent long-running requests:
import "rivaas.dev/router/middleware/timeout"
// Global timeout
a.Use(timeout.New(timeout.WithDuration(5 * time.Second)))
// Skip for streaming endpoints
a.Use(timeout.New(
timeout.WithDuration(5 * time.Second),
timeout.WithSkipPaths("/stream", "/sse"),
))
Recovery
Automatically recover from panics (included by default):
import "rivaas.dev/router/middleware/recovery"
// Custom recovery with stack traces
a.Use(recovery.New(
recovery.WithStackTrace(true),
recovery.WithHandler(func(c *router.Context, err any) {
log.Printf("Panic recovered: %v", err)
c.JSON(http.StatusInternalServerError, map[string]string{
"error": "Internal server error",
})
}),
))
Rate Limiting
Limit request rate (single-instance only):
import "rivaas.dev/router/middleware/ratelimit"
// 100 requests per second with burst of 20
a.Use(ratelimit.New(
ratelimit.WithRequestsPerSecond(100),
ratelimit.WithBurst(20),
))
Production Note
This uses in-memory storage. For multi-instance deployments, use a distributed rate limiter (Redis, etc.).
Middleware Execution Order
Middleware executes in the order it’s registered:
a.Use(middleware1) // Executes first
a.Use(middleware2) // Executes second
a.Use(middleware3) // Executes third
a.GET("/", handler) // Executes last (if all middleware calls Next())
Example Flow:
Request
→ middleware1 (before)
→ middleware2 (before)
→ middleware3 (before)
→ handler
← middleware3 (after)
← middleware2 (after)
← middleware1 (after)
Response
Best Practices:
- Recovery should be first (catches panics from other middleware)
- Logging early (captures all requests)
- Auth before business logic
- CORS early (handles preflight requests)
Example:
a.Use(recovery.New()) // 1. Panic recovery
a.Use(requestid.New()) // 2. Request tracking
a.Use(cors.New(...)) // 3. CORS handling
// App-level observability is automatic
// Route handlers execute last
Creating Custom Middleware
Simple middleware example:
func timingMiddleware() app.HandlerFunc {
return func(c *app.Context) {
start := time.Now()
// Process request (call next middleware/handler)
c.Next()
// After handler executes
duration := time.Since(start)
log.Printf("%s %s - %v", c.Request.Method, c.Request.URL.Path, duration)
}
}
// Use it
a.Use(timingMiddleware())
Authentication middleware example:
func authMiddleware(c *app.Context) {
token := c.Request.Header.Get("Authorization")
if token == "" {
c.JSON(http.StatusUnauthorized, map[string]string{
"error": "Missing authorization token",
})
return // Don't call Next() - stop here
}
// Validate token (simplified)
if !isValidToken(token) {
c.JSON(http.StatusUnauthorized, map[string]string{
"error": "Invalid token",
})
return
}
// Token is valid - continue to handler
c.Next()
}
// Use it
api := a.Group("/api", authMiddleware)
Middleware with Configuration
Use functional options for configurable middleware:
type Config struct {
MaxRequests int
Window time.Duration
}
type Option func(*Config)
func WithMaxRequests(max int) Option {
return func(c *Config) {
c.MaxRequests = max
}
}
func New(opts ...Option) app.HandlerFunc {
cfg := &Config{
MaxRequests: 100,
Window: time.Minute,
}
for _, opt := range opts {
opt(cfg)
}
return func(c *app.Context) {
// Use cfg.MaxRequests, cfg.Window
c.Next()
}
}
// Use it
a.Use(New(
WithMaxRequests(200),
))
Default Middleware
Rivaas automatically includes some middleware based on environment:
Development Mode:
- ✅ Recovery middleware (panic recovery)
- ✅ Access logging via observability recorder
Production Mode:
- ✅ Recovery middleware
- ✅ Error-only logging via observability recorder
Disable defaults:
a, err := app.New(
app.WithMiddleware(), // Empty = no defaults
)
// Now add only what you need
a.Use(recovery.New())
a.Use(requestid.New())
Complete Example
Here’s a production-ready middleware setup:
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"rivaas.dev/app"
"rivaas.dev/router/middleware/cors"
"rivaas.dev/router/middleware/recovery"
"rivaas.dev/router/middleware/requestid"
"rivaas.dev/router/middleware/timeout"
)
func main() {
a, err := app.New(
app.WithServiceName("my-api"),
app.WithEnvironment("production"),
)
if err != nil {
log.Fatal(err)
}
// Global middleware (order matters!)
a.Use(recovery.New(recovery.WithStackTrace(true)))
a.Use(requestid.New())
a.Use(cors.New(cors.WithAllowedOrigins([]string{"https://example.com"})))
a.Use(timeout.New(timeout.WithDuration(30 * time.Second)))
// Public routes
a.GET("/", handlePublic)
a.GET("/health", handleHealth)
// Protected API
api := a.Group("/api", authMiddleware)
api.GET("/users", getUsers)
api.POST("/users", createUser)
// Admin routes
admin := a.Group("/admin", authMiddleware, adminMiddleware)
admin.GET("/dashboard", getDashboard)
// Start server
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer cancel()
if err := a.Start(ctx); err != nil {
log.Fatal(err)
}
}
func handlePublic(c *app.Context) {
c.JSON(http.StatusOK, map[string]string{"status": "ok"})
}
func handleHealth(c *app.Context) {
c.JSON(http.StatusOK, map[string]string{"status": "healthy"})
}
func authMiddleware(c *app.Context) {
token := c.Request.Header.Get("Authorization")
if token == "" || !isValidToken(token) {
c.JSON(http.StatusUnauthorized, map[string]string{"error": "Unauthorized"})
return
}
c.Next()
}
func adminMiddleware(c *app.Context) {
// Check if user is admin (simplified)
if !isAdmin(c.Request.Header.Get("Authorization")) {
c.JSON(http.StatusForbidden, map[string]string{"error": "Forbidden"})
return
}
c.Next()
}
func getUsers(c *app.Context) {
c.JSON(http.StatusOK, []string{"user1", "user2"})
}
func createUser(c *app.Context) {
c.JSON(http.StatusCreated, map[string]string{"status": "created"})
}
func getDashboard(c *app.Context) {
c.JSON(http.StatusOK, map[string]string{"dashboard": "data"})
}
func isValidToken(token string) bool {
// Implement your token validation
return token != ""
}
func isAdmin(token string) bool {
// Implement your admin check
return true
}
Troubleshooting
Middleware Not Executing
Problem: Middleware doesn’t run.
Solutions:
- Ensure middleware is added before routes:
a.Use(...) then a.GET(...) - Check if middleware calls
c.Next() to continue the chain - Verify middleware isn’t returning early without calling
c.Next()
Middleware Running in Wrong Order
Problem: Authentication runs after handler.
Solution: Add middleware in the correct order - they execute top to bottom:
a.Use(recovery.New()) // First
a.Use(authMiddleware) // Second
a.GET("/", handler) // Last
CORS Preflight Failing
Problem: OPTIONS requests return 404.
Solution: Add CORS middleware before routes, and ensure it handles OPTIONS:
a.Use(cors.New(
cors.WithAllowedMethods([]string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}),
))
Next Steps
Learn More
5 - Next Steps
Continue learning Rivaas
You’ve completed the Getting Started guide. You now know how to install Rivaas, build applications, configure them, and add middleware.
What You’ve Learned
✅ Installation — Set up Rivaas and verified it works
✅ First Application — Built a REST API with routes and JSON responses
✅ Configuration — Configured service metadata, health checks, and observability
✅ Middleware — Added functionality like CORS and authentication
Choose Your Path
🚀 Building Production APIs
Learn advanced routing, error handling, and API patterns:
Recommended Example: Blog API — Full-featured blog with CRUD operations, validation, and testing.
📊 Observability & Monitoring
Understand your application in production:
Key Pattern: The observability trinity (logs, metrics, traces) works together. They provide complete visibility into your application.
🔒 Security & Best Practices
Secure your APIs:
Security Checklist:
- ✅ Use HTTPS in production
- ✅ Validate all inputs
- ✅ Implement authentication
- ✅ Add rate limiting
- ✅ Enable security headers
☁️ Deployment & Operations
Deploy your application to production:
Production Checklist:
- ✅ Set up health endpoints
- ✅ Configure timeouts
- ✅ Enable observability
- ✅ Use environment variables
- ✅ Implement graceful shutdown
🎯 Advanced Topics
Deep dive into framework internals:
Example Applications
Learn from complete, production-ready examples:
Quick Start Example
Path: /app/examples/01-quick-start
Complexity: Beginner
Shows: Minimal setup, basic routing, health checks
cd app/examples/01-quick-start
go run main.go
Blog API Example
Path: /app/examples/02-blog
Complexity: Intermediate
Shows: CRUD operations, validation, OpenAPI, testing, configuration
cd app/examples/02-blog
go run main.go
# Visit http://localhost:8080/docs for Swagger UI
Features:
- Complete REST API (posts, authors, comments)
- Method-based validation
- OpenAPI documentation
- Comprehensive tests
- Configuration management
- Observability setup
More Examples
Framework Packages
Rivaas is modular — use any package independently:
Core Packages
| Package | Description | Go Reference |
|---|
| app | Batteries-included framework | pkg.go.dev |
| router | High-performance HTTP router | pkg.go.dev |
Data Handling
| Package | Description | Go Reference |
|---|
| binding | Request binding (JSON, XML, YAML, etc.) | pkg.go.dev |
| validation | Struct validation with JSON Schema | pkg.go.dev |
Observability
API & Errors
| Package | Description | Go Reference |
|---|
| openapi | OpenAPI 3.0/3.1 generation | pkg.go.dev |
| errors | Error formatting (RFC 9457, JSON:API) | pkg.go.dev |
Learn More: Package Documentation
Reference Documentation
Quick access to API references:
Get Help
- 💬 GitHub Discussions — Ask questions, share ideas
- 🐛 GitHub Issues — Report bugs, request features
- 📧 Email — security@rivaas.dev (security issues only)
Contribute
Rivaas is open source and welcomes contributions:
Stay Updated
Quick Reference Card
Create Application
a, err := app.New(
app.WithServiceName("my-api"),
app.WithServiceVersion("v1.0.0"),
)
Register Routes
a.GET("/path", handler)
a.POST("/path", handler)
a.PUT("/path/:id", handler)
a.DELETE("/path/:id", handler)
Add Middleware
a.Use(middleware1, middleware2)
api := a.Group("/api", authMiddleware)
Start Server
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer cancel()
a.Start(ctx)
Handle Requests
func handler(c *app.Context) {
// Get path parameter
id := c.Param("id")
// Get query parameter
filter := c.Query("filter")
// Bind request body (auto-detects JSON, form, etc.)
var req MyRequest
if err := c.Bind(&req); err != nil {
c.JSON(400, map[string]string{"error": "Invalid request"})
return
}
// Send JSON response
c.JSON(200, map[string]string{"status": "ok"})
}
What’s Next?
Pick the topic that interests you most. The documentation works for both linear reading and jumping to specific topics.
Happy building with Rivaas!