Router

Group Routing

Organize routes with prefixes, shared middleware, and hierarchical structure

Route groups allow you to organize related routes under a common prefix and apply shared middleware, making your API structure clean and maintainable.

Basic Route Groups

Create a route group with a common prefix:

package main

import (
    "github.com/xraph/forge"
)

func main() {
    app := forge.New()
    
    // Create API v1 group
    v1 := app.Group("/api/v1")
    
    // All routes in this group will have /api/v1 prefix
    v1.GET("/users", getUsersHandler)
    v1.POST("/users", createUserHandler)
    v1.GET("/users/:id", getUserHandler)
    v1.PUT("/users/:id", updateUserHandler)
    v1.DELETE("/users/:id", deleteUserHandler)
    
    app.Start(":8080")
}

Nested Groups

Groups can be nested to create hierarchical route structures:

func setupRoutes(app *forge.App) {
    // Main API group
    api := app.Group("/api")
    
    // Version 1 group
    v1 := api.Group("/v1")
    v1.GET("/health", healthHandler)
    
    // User routes under v1
    users := v1.Group("/users")
    users.GET("", listUsersHandler)        // GET /api/v1/users
    users.POST("", createUserHandler)      // POST /api/v1/users
    users.GET("/:id", getUserHandler)      // GET /api/v1/users/:id
    users.PUT("/:id", updateUserHandler)   // PUT /api/v1/users/:id
    users.DELETE("/:id", deleteUserHandler) // DELETE /api/v1/users/:id
    
    // Admin routes under users
    admin := users.Group("/admin")
    admin.GET("/stats", getUserStatsHandler)     // GET /api/v1/users/admin/stats
    admin.POST("/bulk", bulkCreateUsersHandler)  // POST /api/v1/users/admin/bulk
    
    // Version 2 group with different structure
    v2 := api.Group("/v2")
    v2.GET("/users", listUsersV2Handler)
    v2.GET("/users/:id/profile", getUserProfileHandler)
}

Group Middleware

Apply middleware to all routes in a group:

import "github.com/xraph/forge/middleware"

func setupAuthenticatedRoutes(app *forge.App) {
    // Create authenticated group with middleware
    auth := app.Router().Group("/auth", 
        forge.WithGroupMiddleware(jwtAuthMiddleware("secret")),
    )
    
    auth.GET("/profile", getProfileHandler)
    auth.PUT("/profile", updateProfileHandler)
    auth.POST("/logout", logoutHandler)
    
    // All routes above require authentication
}

// Authentication middleware
func jwtAuthMiddleware(secretKey string) forge.Middleware {
    return func(next forge.Handler) forge.Handler {
        return func(ctx forge.Context) error {
            token := ctx.Request().Header.Get("Authorization")
            if token == "" {
                return forge.Unauthorized("authentication required")
            }
            
            // Validate token logic here
            // Store user info in context
            ctx.Set("user_id", extractedUserID)
            
            return next(ctx)
        }
    }
}
import "github.com/xraph/forge/middleware"

func setupSecureAPI(app *forge.App) {
    // Apply multiple middleware to group
    limiter := middleware.NewRateLimiter(100, 200)
    
    secure := app.Router().Group("/secure", 
        forge.WithGroupMiddleware(middleware.RequestID()),
        forge.WithGroupMiddleware(jwtAuthMiddleware("secret")),
        forge.WithGroupMiddleware(middleware.RateLimit(limiter, app.Logger())),
        forge.WithGroupMiddleware(middleware.Logging(app.Logger())),
    )
    
    secure.GET("/data", getSecureDataHandler)
    secure.POST("/upload", uploadSecureFileHandler)
}

// Custom middleware
func customLoggingMiddleware(logger forge.Logger) forge.Middleware {
    return func(next forge.Handler) forge.Handler {
        return func(ctx forge.Context) error {
            start := time.Now()
            
            err := next(ctx)
            
            duration := time.Since(start)
            logger.Info("request completed",
                "path", ctx.Request().URL.Path,
                "duration", duration,
            )
            
            return err
        }
    }
}
import "github.com/xraph/forge/middleware"

func setupWithBuiltInMiddleware(app *forge.App) {
    // Admin group with built-in middleware
    adminLimiter := middleware.NewRateLimiter(10, 20)
    
    admin := app.Router().Group("/admin",
        forge.WithGroupMiddleware(middleware.RequestID()),
        forge.WithGroupMiddleware(middleware.Recovery(app.Logger())),
        forge.WithGroupMiddleware(jwtAuthMiddleware("secret")),
        forge.WithGroupMiddleware(requireAdminRole()),
        forge.WithGroupMiddleware(middleware.RateLimit(adminLimiter, app.Logger())),
    )
    
    admin.GET("/users", adminListUsersHandler)
    admin.DELETE("/users/:id", adminDeleteUserHandler)
    
    // Moderator group with different permissions
    mod := app.Group("/mod",
        forge.WithGroupMiddleware(authMiddleware),
        forge.WithGroupMiddleware(requireModeratorRole),
    )
    
    mod.GET("/reports", getReportsHandler)
    mod.PUT("/reports/:id", updateReportHandler)
}

func requireAdminRole(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        user := getUserFromContext(r.Context())
        if user.Role != "admin" {
            http.Error(w, "Forbidden", http.StatusForbidden)
            return
        }
        next.ServeHTTP(w, r)
    })
}

Group Configuration Options

Configure groups with various options:

func setupConfiguredGroups(app *forge.App) {
    // Group with metadata and tags
    api := app.Router().Group("/api/v1",
        forge.WithGroupTags("api", "v1"),
        forge.WithGroupMetadata("version", "1.0.0"),
        forge.WithGroupMetadata("deprecated", false),
    )
    
    // Internal admin group excluded from schemas
    admin := app.Router().Group("/internal/admin",
        forge.WithGroupSchemaExclude(), // Excludes from OpenAPI/AsyncAPI/oRPC
        forge.WithGroupMiddleware(adminAuthMiddleware()),
    )
    
    admin.GET("/debug", debugHandler)
    admin.DELETE("/cache", flushCacheHandler)
    // These routes won't appear in API documentation
}

Schema Exclusion

Exclude entire route groups from API documentation:

// Internal endpoints not for public documentation
internalGroup := app.Router().Group("/internal",
    forge.WithGroupSchemaExclude(),
)

internalGroup.GET("/health", internalHealthHandler)
internalGroup.GET("/metrics", metricsHandler)
internalGroup.POST("/reload", reloadConfigHandler)

// Debug group excluded from schemas
debugGroup := app.Router().Group("/debug",
    forge.WithGroupSchemaExclude(),
    forge.WithGroupMiddleware(debugAuthMiddleware()),
)

debugGroup.GET("/vars", debugVarsHandler)
debugGroup.GET("/pprof", pprofHandler)

Use Cases for Schema Exclusion:

  • Internal/debug endpoints
  • Health checks and monitoring
  • Admin-only routes
  • Development-only endpoints
  • Metrics and profiling routes

Authentication and Authorization Groups

Organize routes by authentication requirements:

func setupAuthGroups(app *forge.App) {
    // Public routes (no authentication)
    public := app.Group("/public")
    public.GET("/health", healthHandler)
    public.GET("/docs", docsHandler)
    public.POST("/register", registerHandler)
    public.POST("/login", loginHandler)
    
    // Authenticated routes
    auth := app.Group("/api",
        forge.WithGroupMiddleware(authMiddleware),
    )
    
    // User routes (authenticated users)
    user := auth.Group("/user")
    user.GET("/profile", getProfileHandler)
    user.PUT("/profile", updateProfileHandler)
    user.GET("/orders", getUserOrdersHandler)
    
    // Admin routes (admin users only)
    admin := auth.Group("/admin",
        forge.WithGroupMiddleware(requireAdminRole),
    )
    admin.GET("/users", adminListUsersHandler)
    admin.GET("/analytics", adminAnalyticsHandler)
    admin.POST("/maintenance", maintenanceModeHandler)
    
    // API key routes (service-to-service)
    apiKey := app.Group("/service",
        forge.WithGroupMiddleware(apiKeyMiddleware),
    )
    apiKey.POST("/webhook", webhookHandler)
    apiKey.GET("/metrics", metricsHandler)
}

Versioned API Groups

Manage API versions with groups:

func setupVersionedAPI(app *forge.App) {
    // Version 1 - Legacy API
    v1 := app.Group("/api/v1",
        forge.WithGroupMiddleware(deprecationWarningMiddleware),
        forge.WithGroupTags("v1", "deprecated"),
    )
    v1.GET("/users", getUsersV1Handler)
    v1.GET("/posts", getPostsV1Handler)
    
    // Version 2 - Current API
    v2 := app.Group("/api/v2",
        forge.WithGroupTags("v2", "current"),
    )
    v2.GET("/users", getUsersV2Handler)
    v2.GET("/posts", getPostsV2Handler)
    v2.GET("/users/:id/posts", getUserPostsHandler) // New endpoint
    
    // Version 3 - Beta API
    v3 := app.Group("/api/v3",
        forge.WithGroupMiddleware(betaAccessMiddleware),
        forge.WithGroupTags("v3", "beta"),
    )
    v3.GET("/users", getUsersV3Handler)
    v3.GET("/posts", getPostsV3Handler)
    v3.GET("/analytics", analyticsHandler) // New feature
}

func deprecationWarningMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("X-API-Deprecated", "true")
        w.Header().Set("X-API-Sunset", "2024-12-31")
        next.ServeHTTP(w, r)
    })
}

Resource-Based Groups

Organize routes around resources:

func setupResourceGroups(app *forge.App) {
    api := app.Group("/api/v1")
    
    // User resource
    users := api.Group("/users")
    users.GET("", listUsersHandler)           // GET /api/v1/users
    users.POST("", createUserHandler)         // POST /api/v1/users
    users.GET("/:id", getUserHandler)         // GET /api/v1/users/:id
    users.PUT("/:id", updateUserHandler)      // PUT /api/v1/users/:id
    users.DELETE("/:id", deleteUserHandler)   // DELETE /api/v1/users/:id
    
    // User sub-resources
    users.GET("/:id/posts", getUserPostsHandler)       // GET /api/v1/users/:id/posts
    users.GET("/:id/followers", getUserFollowersHandler) // GET /api/v1/users/:id/followers
    
    // Post resource
    posts := api.Group("/posts")
    posts.GET("", listPostsHandler)
    posts.POST("", createPostHandler)
    posts.GET("/:id", getPostHandler)
    posts.PUT("/:id", updatePostHandler)
    posts.DELETE("/:id", deletePostHandler)
    
    // Post sub-resources
    posts.GET("/:id/comments", getPostCommentsHandler)
    posts.POST("/:id/comments", createCommentHandler)
    posts.POST("/:id/like", likePostHandler)
    posts.DELETE("/:id/like", unlikePostHandler)
}

Middleware Inheritance

Groups inherit middleware from parent groups:

func setupMiddlewareInheritance(app *forge.App) {
    // Root level middleware
    app.Use(corsMiddleware)
    app.Use(requestIDMiddleware)
    
    // API group inherits root middleware + adds auth
    api := app.Group("/api",
        forge.WithGroupMiddleware(authMiddleware),
    )
    
    // V1 group inherits API middleware + adds versioning
    v1 := api.Group("/v1",
        forge.WithGroupMiddleware(versioningMiddleware("v1")),
    )
    
    // Admin group inherits V1 middleware + adds admin check
    admin := v1.Group("/admin",
        forge.WithGroupMiddleware(requireAdminRole),
    )
    
    // Final middleware chain for /api/v1/admin/* routes:
    // corsMiddleware -> requestIDMiddleware -> authMiddleware -> 
    // versioningMiddleware -> requireAdminRole -> handler
    
    admin.GET("/users", adminUsersHandler)
}

Error Handling in Groups

Handle errors consistently across grouped routes:

func setupErrorHandling(app *forge.App) {
    // API group with error handling middleware
    api := app.Group("/api",
        forge.WithGroupMiddleware(errorHandlingMiddleware),
        forge.WithGroupMiddleware(validationMiddleware),
    )
    
    api.POST("/users", createUserHandler)
    api.PUT("/users/:id", updateUserHandler)
}

func errorHandlingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        defer func() {
            if err := recover(); err != nil {
                log.Printf("Panic in handler: %v", err)
                http.Error(w, "Internal Server Error", http.StatusInternalServerError)
            }
        }()
        
        next.ServeHTTP(w, r)
    })
}

func validationMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        if r.Method == "POST" || r.Method == "PUT" {
            contentType := r.Header.Get("Content-Type")
            if !strings.Contains(contentType, "application/json") {
                http.Error(w, "Content-Type must be application/json", http.StatusBadRequest)
                return
            }
        }
        
        next.ServeHTTP(w, r)
    })
}

Best Practices for Route Groups

  1. Logical Organization: Group routes by functionality, version, or resource
  2. Middleware Reuse: Apply common middleware at the group level
  3. Consistent Naming: Use clear, consistent prefixes for groups
  4. Security Layers: Apply authentication/authorization at appropriate group levels
  5. Version Management: Use groups to manage API versions effectively

Advanced Group Features

Group Metadata and Tags

func setupAdvancedGroups(app *forge.App) {
    // Group with comprehensive metadata
    api := app.Group("/api/v1",
        forge.WithGroupTags("api", "v1", "production"),
        forge.WithGroupMetadata(map[string]any{
            "version":     "1.0.0",
            "maintainer":  "api-team@company.com",
            "deprecated":  false,
            "rate_limit":  1000,
            "description": "Main API endpoints",
        }),
    )
    
    api.GET("/users", getUsersHandler)
}

Dynamic Group Configuration

func setupDynamicGroups(app *forge.App, config *Config) {
    // Configure groups based on environment
    var middleware []forge.Middleware
    
    if config.Environment == "development" {
        middleware = append(middleware, debugMiddleware)
    }
    
    if config.RateLimitEnabled {
        middleware = append(middleware, rateLimitMiddleware)
    }
    
    api := app.Group("/api", forge.WithGroupMiddleware(middleware...))
    api.GET("/health", healthHandler)
}

Next Steps

How is this guide?

Last updated on