Router

Route Inspection & Debugging

Learn how to inspect routes, manage metadata, and debug your Forge router applications

The Forge router provides comprehensive inspection and debugging capabilities to help you understand, monitor, and troubleshoot your application's routing behavior.

Route Inspection

Basic Route Information

The router provides several methods to inspect registered routes:

// Get all registered routes
routes := router.Routes()

for _, route := range routes {
    fmt.Printf("Route: %s %s\n", route.Method, route.Path)
    fmt.Printf("  Name: %s\n", route.Name)
    fmt.Printf("  Tags: %v\n", route.Tags)
    fmt.Printf("  Summary: %s\n", route.Summary)
    fmt.Printf("  Description: %s\n", route.Description)
    fmt.Printf("  Middleware: %d handlers\n", len(route.Middleware))
    fmt.Printf("  Metadata: %v\n", route.Metadata)
}
// Find route by name
route, found := router.RouteByName("user-profile")
if found {
    fmt.Printf("Found route: %s %s\n", route.Method, route.Path)
    fmt.Printf("Handler: %T\n", route.Handler)
}
// Find routes by tag
apiRoutes := router.RoutesByTag("api")
for _, route := range apiRoutes {
    fmt.Printf("API Route: %s %s\n", route.Method, route.Path)
}

// Find admin routes
adminRoutes := router.RoutesByTag("admin")
for _, route := range adminRoutes {
    fmt.Printf("Admin Route: %s %s\n", route.Method, route.Path)
}
// Find routes by metadata
authRoutes := router.RoutesByMetadata("auth", "required")
for _, route := range authRoutes {
    fmt.Printf("Auth Required: %s %s\n", route.Method, route.Path)
}

// Find routes by version
v1Routes := router.RoutesByMetadata("version", "v1")
for _, route := range v1Routes {
    fmt.Printf("V1 Route: %s %s\n", route.Method, route.Path)
}

RouteInfo Structure

The RouteInfo structure provides comprehensive information about each route:

type RouteInfo struct {
    Name        string                 // Route name
    Method      string                 // HTTP method
    Path        string                 // Registered path
    Pattern     string                 // Path pattern with parameters
    Handler     any                    // Handler function/controller
    Middleware  []Middleware           // Applied middleware
    Tags        []string               // Route tags
    Metadata    map[string]any         // Custom metadata
    Extensions  map[string]Extension   // Route extensions
    Summary     string                 // Short description
    Description string                 // Detailed description
}

Route Metadata

Setting Metadata

Add custom metadata to routes for organization and inspection:

// Add metadata to individual routes
router.GET("/api/users", getUsersHandler,
    forge.WithName("list-users"),
    forge.WithTags("api", "users"),
    forge.WithMetadata(map[string]any{
        "auth":        "required",
        "version":     "v1",
        "rate_limit":  100,
        "cache_ttl":   300,
        "public":      false,
        "deprecated":  false,
    }),
)

// Add metadata to groups
apiGroup := router.Group("/api",
    forge.WithGroupTags("api"),
    forge.WithGroupMetadata(map[string]any{
        "version":    "v1",
        "auth":       "required",
        "rate_limit": 1000,
    }),
)
// Define metadata structures
type RouteMetadata struct {
    Auth        AuthConfig    `json:"auth"`
    RateLimit   RateLimitConfig `json:"rate_limit"`
    Cache       CacheConfig   `json:"cache"`
    Monitoring  MonitoringConfig `json:"monitoring"`
}

type AuthConfig struct {
    Required bool     `json:"required"`
    Roles    []string `json:"roles"`
    Scopes   []string `json:"scopes"`
}

// Use structured metadata
metadata := RouteMetadata{
    Auth: AuthConfig{
        Required: true,
        Roles:    []string{"user", "admin"},
        Scopes:   []string{"read:users"},
    },
    RateLimit: RateLimitConfig{
        RequestsPerMinute: 60,
        BurstSize:        10,
    },
}

router.GET("/api/users", getUsersHandler,
    forge.WithMetadata(map[string]any{
        "config": metadata,
    }),
)
// Add metadata dynamically based on conditions
func addConditionalMetadata(route string, handler any) []forge.RouteOption {
    opts := []forge.RouteOption{
        forge.WithName(route),
    }
    
    // Add auth metadata for protected routes
    if strings.HasPrefix(route, "/admin") {
        opts = append(opts,
            forge.WithTags("admin", "protected"),
            forge.WithMetadata(map[string]any{
                "auth":       "admin",
                "audit_log":  true,
                "rate_limit": 10,
            }),
        )
    }
    
    // Add public metadata for public routes
    if strings.HasPrefix(route, "/public") {
        opts = append(opts,
            forge.WithTags("public"),
            forge.WithMetadata(map[string]any{
                "auth":       "none",
                "cache_ttl":  3600,
                "rate_limit": 1000,
            }),
        )
    }
    
    return opts
}

// Use dynamic metadata
router.GET("/admin/users", adminUsersHandler,
    addConditionalMetadata("/admin/users", adminUsersHandler)...)

Querying Metadata

Use metadata for runtime decisions and inspection:

// Middleware that uses metadata
func AuthMiddleware() forge.Middleware {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            // Get current route info (implementation specific)
            route := getCurrentRoute(r)
            
            // Check auth requirement from metadata
            if authRequired, ok := route.Metadata["auth"].(string); ok {
                if authRequired == "required" || authRequired == "admin" {
                    // Perform authentication
                    if !isAuthenticated(r) {
                        http.Error(w, "Unauthorized", http.StatusUnauthorized)
                        return
                    }
                    
                    // Check admin role for admin routes
                    if authRequired == "admin" && !isAdmin(r) {
                        http.Error(w, "Forbidden", http.StatusForbidden)
                        return
                    }
                }
            }
            
            next.ServeHTTP(w, r)
        })
    }
}

Debugging Features

Route Registration Debugging

Debug route registration issues:

// Enable debug logging during route registration
logger := forge.NewLogger(forge.LogConfig{
    Level: forge.LogLevelDebug,
})

app := forge.NewApp(forge.AppConfig{
    Logger: logger,
})

// Routes will be logged as they're registered
router := app.Router()
router.GET("/debug", debugHandler) // Logs: "Registered route: GET /debug"
// Check for duplicate routes
func checkDuplicateRoutes(router forge.Router) {
    routes := router.Routes()
    seen := make(map[string]bool)
    
    for _, route := range routes {
        key := route.Method + " " + route.Path
        if seen[key] {
            fmt.Printf("WARNING: Duplicate route detected: %s\n", key)
        }
        seen[key] = true
    }
}

// Call after all routes are registered
checkDuplicateRoutes(router)
// Detect potential route conflicts
func detectRouteConflicts(router forge.Router) {
    routes := router.Routes()
    
    for i, route1 := range routes {
        for j, route2 := range routes[i+1:] {
            if route1.Method == route2.Method {
                if couldConflict(route1.Pattern, route2.Pattern) {
                    fmt.Printf("POTENTIAL CONFLICT:\n")
                    fmt.Printf("  Route 1: %s %s\n", route1.Method, route1.Pattern)
                    fmt.Printf("  Route 2: %s %s\n", route2.Method, route2.Pattern)
                }
            }
        }
    }
}

func couldConflict(pattern1, pattern2 string) bool {
    // Simple conflict detection logic
    // In practice, this would be more sophisticated
    return strings.Contains(pattern1, ":") || strings.Contains(pattern2, ":")
}

Runtime Debugging

Debug runtime routing behavior:

// Middleware to trace request routing
func RequestTracingMiddleware() forge.Middleware {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            start := time.Now()
            
            // Log request start
            fmt.Printf("[TRACE] %s %s - Started\n", r.Method, r.URL.Path)
            
            // Wrap response writer to capture status
            wrapped := &responseWriter{ResponseWriter: w, statusCode: 200}
            
            // Process request
            next.ServeHTTP(wrapped, r)
            
            // Log request completion
            duration := time.Since(start)
            fmt.Printf("[TRACE] %s %s - Completed in %v (Status: %d)\n",
                r.Method, r.URL.Path, duration, wrapped.statusCode)
        })
    }
}

type responseWriter struct {
    http.ResponseWriter
    statusCode int
}

func (rw *responseWriter) WriteHeader(code int) {
    rw.statusCode = code
    rw.ResponseWriter.WriteHeader(code)
}
// Debug middleware execution order
func DebugMiddleware(name string) forge.Middleware {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            fmt.Printf("[MIDDLEWARE] %s - Before\n", name)
            next.ServeHTTP(w, r)
            fmt.Printf("[MIDDLEWARE] %s - After\n", name)
        })
    }
}

// Apply debug middleware
router.Use(DebugMiddleware("Global"))
apiGroup := router.Group("/api")
apiGroup.Use(DebugMiddleware("API Group"))
apiGroup.GET("/users", usersHandler, forge.WithMiddleware(DebugMiddleware("Route Specific")))

// Output for GET /api/users:
// [MIDDLEWARE] Global - Before
// [MIDDLEWARE] API Group - Before  
// [MIDDLEWARE] Route Specific - Before
// [MIDDLEWARE] Route Specific - After
// [MIDDLEWARE] API Group - After
// [MIDDLEWARE] Global - After
// Debug route parameter extraction
func ParameterDebugHandler(ctx forge.Context) error {
    fmt.Printf("[PARAMS] Route: %s\n", ctx.Request().URL.Path)
    
    // Log all parameters
    params := ctx.Params()
    for key, value := range params {
        fmt.Printf("[PARAMS] %s = %s\n", key, value)
    }
    
    // Log query parameters
    query := ctx.Request().URL.Query()
    for key, values := range query {
        fmt.Printf("[QUERY] %s = %v\n", key, values)
    }
    
    return ctx.JSON(200, map[string]any{
        "path_params":  params,
        "query_params": query,
    })
}

router.GET("/debug/:id/items/:item", ParameterDebugHandler)

Monitoring & Observability

Built-in Metrics

The router provides built-in metrics for monitoring:

// Enable metrics collection
app := forge.NewApp(forge.AppConfig{
    MetricsConfig: forge.MetricsConfig{
        Enabled:     true,
        MetricsPath: "/_/metrics",
        Type:        "prometheus",
    },
})

// Metrics are automatically collected for:
// - Request count by route, method, status
// - Request duration by route
// - Active requests gauge
// - Error rates
// Add custom metrics to routes
func MetricsMiddleware() forge.Middleware {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            // Get metrics from context
            metrics := forge.GetMetrics(r.Context())
            
            // Increment request counter
            counter := metrics.Counter("http_requests_total")
            counter.WithLabels(map[string]string{
                "method": r.Method,
                "path":   r.URL.Path,
            }).Inc()
            
            // Track request duration
            histogram := metrics.Histogram("http_request_duration_seconds")
            start := time.Now()
            defer func() {
                histogram.WithLabels(map[string]string{
                    "method": r.Method,
                    "path":   r.URL.Path,
                }).ObserveDuration(start)
            }()
            
            // Track active requests
            gauge := metrics.Gauge("http_active_requests")
            gauge.Inc()
            defer gauge.Dec()
            
            next.ServeHTTP(w, r)
        })
    }
}
// Get route statistics
func getRouteStatistics(router forge.Router) {
    routes := router.Routes()
    
    for _, route := range routes {
        // Access route metadata for stats
        if stats, ok := route.Metadata["stats"]; ok {
            fmt.Printf("Route: %s %s\n", route.Method, route.Path)
            fmt.Printf("  Stats: %+v\n", stats)
        }
    }
}

// Custom statistics middleware
func StatsMiddleware() forge.Middleware {
    stats := make(map[string]*RouteStats)
    mu := sync.RWMutex{}
    
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            start := time.Now()
            key := r.Method + " " + r.URL.Path
            
            // Update stats
            mu.Lock()
            if stats[key] == nil {
                stats[key] = &RouteStats{}
            }
            stats[key].CallCount++
            mu.Unlock()
            
            // Process request
            wrapped := &responseWriter{ResponseWriter: w, statusCode: 200}
            next.ServeHTTP(wrapped, r)
            
            // Update duration stats
            duration := time.Since(start)
            mu.Lock()
            stats[key].TotalLatency += duration
            stats[key].AverageLatency = stats[key].TotalLatency / time.Duration(stats[key].CallCount)
            if wrapped.statusCode >= 400 {
                stats[key].ErrorCount++
            }
            mu.Unlock()
        })
    }
}

type RouteStats struct {
    CallCount      int64
    ErrorCount     int64
    TotalLatency   time.Duration
    AverageLatency time.Duration
}

Health Checks

Monitor router health and readiness:

// Enable health checks
app := forge.NewApp(forge.AppConfig{
    HealthConfig: forge.HealthConfig{
        Enabled:      true,
        HealthPath:   "/_/health",
        LivenessPath: "/_/health/live",
        ReadinessPath: "/_/health/ready",
    },
})

// Built-in endpoints:
// GET /_/health       - Overall health status
// GET /_/health/live  - Liveness probe (always 200 if server up)
// GET /_/health/ready - Readiness probe (200 if all checks pass)
// Add custom health checks
app.AddHealthCheck("database", func(ctx context.Context) forge.HealthResult {
    // Check database connectivity
    if err := db.Ping(); err != nil {
        return forge.HealthResult{
            Status:  forge.HealthStatusUnhealthy,
            Message: "Database unreachable",
            Error:   err,
        }
    }
    
    return forge.HealthResult{
        Status:  forge.HealthStatusHealthy,
        Message: "Database connected",
    }
})

app.AddHealthCheck("external-api", func(ctx context.Context) forge.HealthResult {
    // Check external API
    resp, err := http.Get("https://api.example.com/health")
    if err != nil || resp.StatusCode != 200 {
        return forge.HealthResult{
            Status:  forge.HealthStatusUnhealthy,
            Message: "External API unreachable",
        }
    }
    
    return forge.HealthResult{
        Status:  forge.HealthStatusHealthy,
        Message: "External API healthy",
    }
})
// Middleware to track route health
func RouteHealthMiddleware() forge.Middleware {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            // Track route health
            healthManager := forge.GetHealthManager(r.Context())
            
            start := time.Now()
            wrapped := &responseWriter{ResponseWriter: w, statusCode: 200}
            
            next.ServeHTTP(wrapped, r)
            
            // Report route health
            duration := time.Since(start)
            routeKey := r.Method + " " + r.URL.Path
            
            if wrapped.statusCode >= 500 {
                healthManager.ReportUnhealthy(routeKey, "Server error")
            } else if duration > 5*time.Second {
                healthManager.ReportDegraded(routeKey, "Slow response")
            } else {
                healthManager.ReportHealthy(routeKey)
            }
        })
    }
}

Debugging Tools

Route Inspector Tool

Create a debugging endpoint to inspect routes:

// Debug endpoint to inspect all routes
router.GET("/_/debug/routes", func(ctx forge.Context) error {
    routes := router.Routes()
    
    type RouteDebugInfo struct {
        Name        string            `json:"name"`
        Method      string            `json:"method"`
        Path        string            `json:"path"`
        Pattern     string            `json:"pattern"`
        HandlerType string            `json:"handler_type"`
        Middleware  int               `json:"middleware_count"`
        Tags        []string          `json:"tags"`
        Metadata    map[string]any    `json:"metadata"`
        Summary     string            `json:"summary"`
        Description string            `json:"description"`
    }
    
    debugInfo := make([]RouteDebugInfo, len(routes))
    for i, route := range routes {
        debugInfo[i] = RouteDebugInfo{
            Name:        route.Name,
            Method:      route.Method,
            Path:        route.Path,
            Pattern:     route.Pattern,
            HandlerType: fmt.Sprintf("%T", route.Handler),
            Middleware:  len(route.Middleware),
            Tags:        route.Tags,
            Metadata:    route.Metadata,
            Summary:     route.Summary,
            Description: route.Description,
        }
    }
    
    return ctx.JSON(200, map[string]any{
        "total_routes": len(routes),
        "routes":       debugInfo,
    })
})

Performance Profiling

Enable performance profiling for debugging:

// Enable profiling
app := forge.NewApp(forge.AppConfig{
    ProfilingConfig: forge.ProfilingConfig{
        Enabled:      true,
        ProfilePath:  "/_/profile",
        CPUProfile:   true,
        MemProfile:   true,
        BlockProfile: true,
        MutexProfile: true,
    },
})

// Available profiling endpoints:
// GET /_/profile/cpu     - CPU profile
// GET /_/profile/heap    - Memory heap profile
// GET /_/profile/block   - Block profile
// GET /_/profile/mutex   - Mutex profile
// GET /_/profile/goroutine - Goroutine profile

Best Practices

Consistent Naming

// Use consistent naming conventions
router.GET("/api/users", getUsersHandler,
    forge.WithName("list-users"),
    forge.WithTags("api", "users"),
)

router.GET("/api/users/:id", getUserHandler,
    forge.WithName("get-user"),
    forge.WithTags("api", "users"),
)

Meaningful Metadata

// Add meaningful metadata for debugging
router.POST("/api/users", createUserHandler,
    forge.WithName("create-user"),
    forge.WithTags("api", "users", "write"),
    forge.WithMetadata(map[string]any{
        "auth":        "required",
        "rate_limit":  10,
        "audit_log":   true,
        "cache_invalidate": []string{"users", "user-count"},
    }),
)

Debug Mode

// Enable debug mode in development
if os.Getenv("DEBUG") == "true" {
    router.Use(RequestTracingMiddleware())
    router.Use(DebugMiddleware("Global"))
}

Monitoring Integration

// Integrate with monitoring systems
router.Use(MetricsMiddleware())
router.Use(TracingMiddleware())
router.Use(LoggingMiddleware())

The inspection and debugging features help you understand your application's routing behavior, troubleshoot issues, and monitor performance in production environments.

How is this guide?

Last updated on