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

Return to the regular view of this page.

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:

1. Installation

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

2. Your First Application

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

3. Configuration

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

4. Using Middleware

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

5. Next Steps

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:

go get rivaas.dev/app

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:

Core
# High-performance router
go get rivaas.dev/router

# Full framework
go get rivaas.dev/app
Data
# Request binding
go get rivaas.dev/binding

# Validation
go get rivaas.dev/validation

# Configuration
go get rivaas.dev/config
Observability
# Structured logging
go get rivaas.dev/logging

# Metrics collection
go get rivaas.dev/metrics

# Distributed tracing
go get rivaas.dev/tracing
API & Errors
# 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:

go run main.go

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:

go get -u rivaas.dev/app

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

go get rivaas.dev/app

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:

go run main.go

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,
    })
})

Request Headers

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 server
  • a.TestJSON(method, path, body) - Test JSON endpoints
  • app.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:

  1. Content-Type header is set to application/json
  2. Request body contains valid JSON
  3. 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

Service Metadata

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:

OptionDescriptionDefault
WithReadTimeoutMaximum time to read request10s
WithWriteTimeoutMaximum time to write response10s
WithIdleTimeoutMaximum idle connection time60s
WithReadHeaderTimeoutMaximum time to read headers2s
WithShutdownTimeoutGraceful shutdown timeout30s

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 probe
  • GET /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:

MiddlewarePurposeProduction-Ready
recoveryPanic recovery✅ Auto-included
requestidRequest ID tracking
corsCross-Origin Resource Sharing
timeoutRequest timeouts
accesslogAccess logging
ratelimitRate limiting⚠️ Single-instance only
basicauthHTTP Basic Auth
bodylimitRequest size limits
compressionResponse compression
securitySecurity headers
methodoverrideHTTP method override
trailingslashTrailing 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),
))

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:

  1. Recovery should be first (catches panics from other middleware)
  2. Logging early (captures all requests)
  3. Auth before business logic
  4. 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

PackageDescriptionGo Reference
appBatteries-included frameworkpkg.go.dev
routerHigh-performance HTTP routerpkg.go.dev

Data Handling

PackageDescriptionGo Reference
bindingRequest binding (JSON, XML, YAML, etc.)pkg.go.dev
validationStruct validation with JSON Schemapkg.go.dev

Observability

PackageDescriptionGo Reference
loggingStructured logging with slogpkg.go.dev
metricsOpenTelemetry metricspkg.go.dev
tracingDistributed tracingpkg.go.dev

API & Errors

PackageDescriptionGo Reference
openapiOpenAPI 3.0/3.1 generationpkg.go.dev
errorsError formatting (RFC 9457, JSON:API)pkg.go.dev

Learn More: Package Documentation

Reference Documentation

Quick access to API references:

Community & Support

Get Help

  • 💬 GitHub Discussions — Ask questions, share ideas
  • 🐛 GitHub Issues — Report bugs, request features
  • 📧 Emailsecurity@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!