Route Parameters
Learn how to use route parameters, path patterns, and route matching in Forge router for dynamic URL handling.
Route Parameters
Forge router provides powerful support for route parameters, allowing you to capture dynamic segments from URLs and use them in your handlers. This enables you to build flexible APIs with dynamic routing patterns.
Basic Route Parameters
Colon Syntax
The most common way to define route parameters is using the colon (:) syntax:
package main
import (
"github.com/xraph/forge"
)
func main() {
app := forge.New()
router := app.Router()
// Single parameter
router.GET("/users/:id", func(ctx *forge.Context) error {
userID := ctx.Param("id")
return ctx.JSON(200, map[string]string{
"user_id": userID,
})
})
// Multiple parameters
router.GET("/users/:userID/posts/:postID", func(ctx *forge.Context) error {
userID := ctx.Param("userID")
postID := ctx.Param("postID")
return ctx.JSON(200, map[string]string{
"user_id": userID,
"post_id": postID,
})
})
app.Start(":8080")
}Brace Syntax
Forge also supports OpenAPI-style brace ({}) syntax for parameters:
// OpenAPI-style parameters
router.GET("/api/v1/users/{id}", func(ctx *forge.Context) error {
userID := ctx.Param("id")
return ctx.JSON(200, map[string]string{
"user_id": userID,
})
})
// Mixed syntax (both work)
router.GET("/organizations/{orgID}/teams/:teamID", func(ctx *forge.Context) error {
orgID := ctx.Param("orgID")
teamID := ctx.Param("teamID")
return ctx.JSON(200, map[string]string{
"organization_id": orgID,
"team_id": teamID,
})
})Parameter Extraction
Accessing Parameters
Use the Param() method to extract route parameters:
router.GET("/products/:category/:id", func(ctx *forge.Context) error {
category := ctx.Param("category")
productID := ctx.Param("id")
// Validate parameters
if category == "" || productID == "" {
return ctx.JSON(400, map[string]string{
"error": "Missing required parameters",
})
}
return ctx.JSON(200, map[string]interface{}{
"category": category,
"product_id": productID,
"path": ctx.Path(),
})
})Getting All Parameters
Retrieve all route parameters at once:
router.GET("/api/:version/users/:userID/settings/:setting", func(ctx *forge.Context) error {
params := ctx.Params()
return ctx.JSON(200, map[string]interface{}{
"all_params": params,
"version": params["version"],
"user_id": params["userID"],
"setting": params["setting"],
})
})Wildcard Routes
Basic Wildcards
Use * for wildcard matching to capture remaining path segments:
// Catch-all wildcard
router.GET("/static/*", func(ctx *forge.Context) error {
filepath := ctx.Param("*")
return ctx.JSON(200, map[string]string{
"requested_file": filepath,
"full_path": "/static/" + filepath,
})
})
// Named wildcard
router.GET("/files/*filepath", func(ctx *forge.Context) error {
filepath := ctx.Param("filepath")
// Handle file serving logic
return ctx.JSON(200, map[string]string{
"file_path": filepath,
})
})Advanced Wildcard Patterns
// Wildcard with prefix matching
router.GET("/dashboard/*", func(ctx *forge.Context) error {
remainingPath := ctx.Param("*")
// Route to different dashboard sections
switch {
case strings.HasPrefix(remainingPath, "analytics"):
return handleAnalytics(ctx, remainingPath)
case strings.HasPrefix(remainingPath, "settings"):
return handleSettings(ctx, remainingPath)
default:
return handleDashboardHome(ctx)
}
})
// Multiple wildcards in groups
adminGroup := router.Group("/admin")
adminGroup.GET("/users/*action", handleUserActions)
adminGroup.GET("/system/*config", handleSystemConfig)Parameter Validation
Type Conversion and Validation
import (
"strconv"
"regexp"
)
// Numeric parameter validation
router.GET("/users/:id", func(ctx *forge.Context) error {
idStr := ctx.Param("id")
// Convert to integer
userID, err := strconv.Atoi(idStr)
if err != nil {
return ctx.JSON(400, map[string]string{
"error": "Invalid user ID format",
})
}
if userID <= 0 {
return ctx.JSON(400, map[string]string{
"error": "User ID must be positive",
})
}
return ctx.JSON(200, map[string]interface{}{
"user_id": userID,
})
})
// UUID parameter validation
var uuidRegex = regexp.MustCompile(`^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$`)
router.GET("/resources/:uuid", func(ctx *forge.Context) error {
uuid := ctx.Param("uuid")
if !uuidRegex.MatchString(uuid) {
return ctx.JSON(400, map[string]string{
"error": "Invalid UUID format",
})
}
return ctx.JSON(200, map[string]string{
"resource_uuid": uuid,
})
})Custom Parameter Validators
// Create reusable parameter validators
func ValidateUserID(ctx *forge.Context) error {
idStr := ctx.Param("id")
if idStr == "" {
return ctx.JSON(400, map[string]string{"error": "User ID required"})
}
id, err := strconv.Atoi(idStr)
if err != nil || id <= 0 {
return ctx.JSON(400, map[string]string{"error": "Invalid user ID"})
}
// Store validated ID in context
ctx.Set("validated_user_id", id)
return nil
}
// Use validator as middleware
router.GET("/users/:id", ValidateUserID, func(ctx *forge.Context) error {
userID := ctx.Get("validated_user_id").(int)
return ctx.JSON(200, map[string]interface{}{
"user_id": userID,
})
})Route Patterns
Pattern Matching
Forge supports various route pattern formats:
// Exact match
router.GET("/api/health", healthCheck)
// Parameter segments
router.GET("/api/users/:id", getUser)
// Multiple parameters
router.GET("/api/users/:userID/posts/:postID/comments/:commentID", getComment)
// Optional segments (using groups)
apiGroup := router.Group("/api/v1")
apiGroup.GET("/users", listUsers) // /api/v1/users
apiGroup.GET("/users/:id", getUser) // /api/v1/users/123
// Wildcard patterns
router.GET("/static/*filepath", serveStatic)
router.GET("/docs/*", serveDocs)Route Priority
Routes are matched in the order they are registered. More specific routes should be registered before more general ones:
// Register specific routes first
router.GET("/users/profile", getCurrentUserProfile)
router.GET("/users/settings", getUserSettings)
router.GET("/users/new", showNewUserForm)
// Then register parameterized routes
router.GET("/users/:id", getUser)
// Finally, wildcard routes
router.GET("/users/*", handleUserWildcard)Advanced Parameter Features
Parameter Constraints
// Custom parameter constraints using middleware
func WithIDConstraint(min, max int) forge.Middleware {
return func(next forge.HandlerFunc) forge.HandlerFunc {
return func(ctx *forge.Context) error {
idStr := ctx.Param("id")
id, err := strconv.Atoi(idStr)
if err != nil {
return ctx.JSON(400, map[string]string{
"error": "Invalid ID format",
})
}
if id < min || id > max {
return ctx.JSON(400, map[string]string{
"error": fmt.Sprintf("ID must be between %d and %d", min, max),
})
}
return next(ctx)
}
}
}
// Apply constraint to routes
router.GET("/users/:id", WithIDConstraint(1, 999999), getUser)Parameter Transformation
// Transform parameters before handler execution
func TransformSlug(ctx *forge.Context) error {
slug := ctx.Param("slug")
// Transform slug to ID (example: lookup in database)
id, err := lookupIDBySlug(slug)
if err != nil {
return ctx.JSON(404, map[string]string{
"error": "Resource not found",
})
}
// Store transformed value
ctx.Set("resource_id", id)
return nil
}
router.GET("/articles/:slug", TransformSlug, func(ctx *forge.Context) error {
resourceID := ctx.Get("resource_id").(int)
return ctx.JSON(200, map[string]interface{}{
"resource_id": resourceID,
})
})Parameter Documentation
For OpenAPI documentation, specify parameter schemas:
import "github.com/xraph/forge"
// Document parameters for OpenAPI
router.GET("/users/:id", getUser,
forge.WithName("getUser"),
forge.WithSummary("Get user by ID"),
forge.WithDescription("Retrieves a user by their unique identifier"),
forge.WithTags("users"),
forge.WithParameter("id", "path", "User ID", true, "123"),
)
// Document query parameters
router.GET("/users", listUsers,
forge.WithName("listUsers"),
forge.WithQuerySchema(&ListUsersQuery{}),
)
type ListUsersQuery struct {
Page int `query:"page" validate:"min=1" example:"1"`
PageSize int `query:"page_size" validate:"min=1,max=100" example:"20"`
Search string `query:"search" example:"john"`
}Error Handling
Parameter Error Handling
// Centralized parameter error handling
func HandleParameterErrors(ctx *forge.Context) error {
// Check for required parameters
requiredParams := []string{"id", "category"}
for _, param := range requiredParams {
if ctx.Param(param) == "" {
return ctx.JSON(400, map[string]interface{}{
"error": "Missing required parameter",
"parameter": param,
"path": ctx.Path(),
})
}
}
return nil
}
// Use in route groups
apiGroup := router.Group("/api/v1")
apiGroup.Use(HandleParameterErrors)
apiGroup.GET("/products/:category/:id", getProduct)Custom Error Responses
// Custom parameter validation errors
type ParameterError struct {
Parameter string `json:"parameter"`
Value string `json:"value"`
Message string `json:"message"`
}
func ValidateProductID(ctx *forge.Context) error {
idStr := ctx.Param("id")
if idStr == "" {
return ctx.JSON(400, ParameterError{
Parameter: "id",
Value: "",
Message: "Product ID is required",
})
}
if len(idStr) < 3 {
return ctx.JSON(400, ParameterError{
Parameter: "id",
Value: idStr,
Message: "Product ID must be at least 3 characters",
})
}
return nil
}Best Practices
Parameter Best Practices
- Consistent Naming: Use consistent parameter naming conventions (camelCase or snake_case)
- Validation: Always validate parameters before using them
- Documentation: Document parameters for API consumers
- Error Handling: Provide clear error messages for invalid parameters
- Type Safety: Convert and validate parameter types early
Recommended Patterns
// 1. Use descriptive parameter names
router.GET("/users/:userID/orders/:orderID", getOrder) // Good
router.GET("/users/:id1/orders/:id2", getOrder) // Avoid
// 2. Group related parameters
router.GET("/api/:version/users/:userID", getUser)
// 3. Use middleware for common validations
router.Use(ValidateAPIVersion)
router.GET("/api/:version/users/:userID", getUser)
// 4. Provide parameter examples in documentation
router.GET("/products/:category/:id", getProduct,
forge.WithParameter("category", "path", "Product category", true, "electronics"),
forge.WithParameter("id", "path", "Product ID", true, "12345"),
)Next Steps
- Learn about Controllers for organizing route handlers
- Explore Middleware for request processing
- Check out Advanced Features for WebSocket and streaming support
- See Examples for complete implementation patterns
How is this guide?
Last updated on