Routing

Master HTTP routing with Forge's flexible router system

Routing

Forge provides a powerful and flexible routing system that supports multiple backends, middleware, and advanced features like OpenAPI generation and validation.

Router Interface

The router interface provides a clean, consistent API regardless of the underlying implementation:

type Router interface {
    // HTTP methods
    GET(path string, handler HandlerFunc, opts ...RouteOption) error
    POST(path string, handler HandlerFunc, opts ...RouteOption) error
    PUT(path string, handler HandlerFunc, opts ...RouteOption) error
    DELETE(path string, handler HandlerFunc, opts ...RouteOption) error
    PATCH(path string, handler HandlerFunc, opts ...RouteOption) error
    HEAD(path string, handler HandlerFunc, opts ...RouteOption) error
    OPTIONS(path string, handler HandlerFunc, opts ...RouteOption) error
    
    // Route groups
    Group(path string, opts ...GroupOption) Router
    
    // Middleware
    Use(middleware ...Middleware) error
    
    // Route information
    Routes() []RouteInfo
}

Basic Routing

Simple Routes

app := forge.NewApp(forge.DefaultAppConfig())

// Basic GET route
app.Router().GET("/", func(ctx forge.Context) error {
    return ctx.JSON(200, map[string]string{"message": "Hello, Forge!"})
})

// POST route with JSON body
app.Router().POST("/users", func(ctx forge.Context) error {
    var user CreateUserRequest
    if err := ctx.BindJSON(&user); err != nil {
        return forge.BadRequest("invalid JSON")
    }
    
    // Process user creation
    return ctx.JSON(201, map[string]string{"id": "123"})
})

// Route with path parameters
app.Router().GET("/users/:id", func(ctx forge.Context) error {
    id := ctx.Param("id")
    return ctx.JSON(200, map[string]string{"id": id})
})

Path Parameters

// Single parameter
app.Router().GET("/users/:id", handler)

// Multiple parameters
app.Router().GET("/users/:userId/posts/:postId", handler)

// Wildcard parameters
app.Router().GET("/files/*path", handler)

// Access parameters in handler
func handler(ctx forge.Context) error {
    userID := ctx.Param("userId")
    postID := ctx.Param("postId")
    path := ctx.Param("path")
    
    return ctx.JSON(200, map[string]string{
        "user_id": userID,
        "post_id": postID,
        "path":    path,
    })
}

Route Groups

Route groups allow you to organize related routes and apply common middleware:

// Create a group with common middleware
api := app.Router().Group("/api/v1", forge.WithGroupMiddleware(
    authMiddleware,
    loggingMiddleware,
))

// All routes in this group will have the middleware applied
api.GET("/users", getUsersHandler)
api.POST("/users", createUserHandler)
api.GET("/users/:id", getUserHandler)
api.PUT("/users/:id", updateUserHandler)
api.DELETE("/users/:id", deleteUserHandler)

// Nested groups
admin := api.Group("/admin", forge.WithGroupAuth("admin"))
admin.GET("/stats", getStatsHandler)
admin.POST("/config", updateConfigHandler)

Middleware

Middleware functions are executed before route handlers and can modify requests and responses:

Global Middleware

// Apply middleware to all routes
app.Router().Use(
    forge.RecoveryMiddleware(),
    forge.LoggingMiddleware(),
    forge.CORSMiddleware(),
)

Route-Specific Middleware

// Middleware for specific routes
app.Router().GET("/protected", handler, forge.WithMiddleware(
    authMiddleware,
    rateLimitMiddleware,
))

Custom Middleware

func authMiddleware(next forge.HandlerFunc) forge.HandlerFunc {
    return func(ctx forge.Context) error {
        token := ctx.Header("Authorization")
        if token == "" {
            return forge.Unauthorized("missing authorization header")
        }
        
        // Validate token
        user, err := validateToken(token)
        if err != nil {
            return forge.Unauthorized("invalid token")
        }
        
        // Set user context
        ctx.Set("user", user)
        return next(ctx)
    }
}

Route Options

Forge provides extensive route options for configuration:

Basic Options

app.Router().GET("/users", handler,
    forge.WithName("getUsers"),
    forge.WithSummary("Get all users"),
    forge.WithDescription("Retrieves a list of all users in the system"),
    forge.WithTags("users", "read"),
)

Timeout and Metadata

app.Router().POST("/upload", handler,
    forge.WithTimeout(30*time.Second),
    forge.WithMetadata("rate_limit", "100/hour"),
    forge.WithMetadata("cache", "no-cache"),
)

Validation

type CreateUserRequest struct {
    Name  string `json:"name" validate:"required,min=2,max=50"`
    Email string `json:"email" validate:"required,email"`
}

app.Router().POST("/users", handler,
    forge.WithValidation(true),
    forge.WithRequestSchema(CreateUserRequest{}),
)

OpenAPI Integration

Forge provides built-in OpenAPI/Swagger generation:

Request Schema

type CreateUserRequest struct {
    Name  string `json:"name" body:"" description:"User name" minLength:"2" maxLength:"50"`
    Email string `json:"email" body:"" description:"Email address" format:"email"`
}

app.Router().POST("/users", handler,
    forge.WithRequestSchema(CreateUserRequest{}),
    forge.WithResponseSchema(201, "User created", UserResponse{}),
    forge.WithErrorResponses(),
)

Response Schemas

type UserResponse struct {
    ID    string `json:"id" description:"User ID"`
    Name  string `json:"name" description:"User name"`
    Email string `json:"email" description:"Email address"`
}

app.Router().GET("/users/:id", handler,
    forge.WithResponseSchema(200, "User details", UserResponse{}),
    forge.WithResponseSchema(404, "User not found", ErrorResponse{}),
)

Advanced OpenAPI Features

// Paginated responses
app.Router().GET("/users", handler,
    forge.WithPaginatedResponse(UserResponse{}, 200),
)

// File upload responses
app.Router().POST("/upload", handler,
    forge.WithFileUploadResponse(201),
)

// Batch operations
app.Router().POST("/users/batch", handler,
    forge.WithBatchResponse(UserResponse{}, 201),
)

Authentication Integration

Forge provides built-in authentication support:

Route-Level Authentication

// Single provider
app.Router().GET("/protected", handler,
    forge.WithAuth("jwt"),
)

// Multiple providers (OR condition)
app.Router().GET("/protected", handler,
    forge.WithAuth("jwt", "api-key"),
)

// Required scopes
app.Router().POST("/admin/users", handler,
    forge.WithRequiredAuth("jwt", "admin", "write:users"),
)

// Multiple providers (AND condition)
app.Router().GET("/high-security", handler,
    forge.WithAuthAnd("jwt", "mfa"),
)

Group-Level Authentication

// Apply auth to all routes in group
api := app.Router().Group("/api", forge.WithGroupAuth("jwt"))

// Apply auth with required scopes
admin := app.Router().Group("/admin", 
    forge.WithGroupAuth("jwt"),
    forge.WithGroupRequiredScopes("admin"),
)

Error Handling

HTTP Error Responses

func getUserHandler(ctx forge.Context) error {
    id := ctx.Param("id")
    
    user, err := userService.GetUser(id)
    if err != nil {
        if errors.Is(err, ErrUserNotFound) {
            return forge.NotFound("user not found")
        }
        return forge.InternalError("failed to get user")
    }
    
    return ctx.JSON(200, user)
}

Custom Error Handlers

app.Router().Use(forge.ErrorMiddleware(func(err error, ctx forge.Context) {
    app.Logger().Error("request failed",
        forge.F("error", err),
        forge.F("path", ctx.Path()),
        forge.F("method", ctx.Method()),
    )
    
    // Custom error response
    if httpErr, ok := err.(forge.HTTPError); ok {
        ctx.JSON(httpErr.StatusCode(), map[string]interface{}{
            "error": httpErr.Message(),
            "code":  httpErr.StatusCode(),
        })
    } else {
        ctx.JSON(500, map[string]string{"error": "internal server error"})
    }
}))

Context and Request Handling

Context Interface

type Context interface {
    // Request information
    Method() string
    Path() string
    Param(name string) string
    Query(name string) string
    Header(name string) string
    
    // Request body
    BindJSON(obj interface{}) error
    BindXML(obj interface{}) error
    BindForm(obj interface{}) error
    
    // Response
    JSON(statusCode int, obj interface{}) error
    XML(statusCode int, obj interface{}) error
    String(statusCode int, format string, values ...interface{}) error
    Data(statusCode int, contentType string, data []byte) error
    
    // Headers and status
    SetHeader(key, value string)
    Status(code int)
    
    // Context storage
    Set(key string, value interface{})
    Get(key string) (interface{}, bool)
    
    // Request context
    Request() *http.Request
    Response() http.ResponseWriter
}

Request Binding

func createUserHandler(ctx forge.Context) error {
    var req CreateUserRequest
    
    // Bind JSON body
    if err := ctx.BindJSON(&req); err != nil {
        return forge.BadRequest("invalid JSON")
    }
    
    // Access query parameters
    page := ctx.Query("page")
    limit := ctx.Query("limit")
    
    // Access headers
    contentType := ctx.Header("Content-Type")
    userAgent := ctx.Header("User-Agent")
    
    // Process request
    user, err := userService.CreateUser(req)
    if err != nil {
        return forge.InternalError("failed to create user")
    }
    
    return ctx.JSON(201, user)
}

Performance Optimization

Route Caching

// Enable route caching for better performance
app := forge.NewApp(forge.AppConfig{
    RouterOptions: []forge.RouterOption{
        forge.WithRouteCaching(true),
    },
})

Middleware Ordering

// Optimize middleware order for performance
app.Router().Use(
    forge.RecoveryMiddleware(),    // First: catch panics
    forge.LoggingMiddleware(),     // Second: log requests
    forge.AuthMiddleware(),        // Third: authentication
    forge.RateLimitMiddleware(),   // Fourth: rate limiting
    forge.ValidationMiddleware(),   // Fifth: validation
)

Route Grouping Strategy

// Group routes by functionality for better organization
users := app.Router().Group("/users")
posts := app.Router().Group("/posts")
comments := app.Router().Group("/comments")

// Apply common middleware per group
users.Use(userMiddleware)
posts.Use(postMiddleware)
comments.Use(commentMiddleware)

Testing Routes

Unit Testing

func TestUserRoutes(t *testing.T) {
    app := forge.NewTestApp(forge.TestAppConfig{
        Name: "test-app",
    })
    
    // Register test services
    app.RegisterService("userService", func(container forge.Container) (interface{}, error) {
        return &MockUserService{}, nil
    })
    
    // Test GET route
    resp := app.Test().GET("/users/123").Expect(t)
    resp.Status(200)
    resp.JSON().Object().Value("id").Equal("123")
    
    // Test POST route
    resp = app.Test().POST("/users").
        JSON(map[string]string{"name": "John", "email": "john@example.com"}).
        Expect(t)
    resp.Status(201)
    resp.JSON().Object().Value("id").NotNull()
}

Integration Testing

func TestUserAPI(t *testing.T) {
    app := forge.NewTestApp(forge.TestAppConfig{
        Name: "test-app",
        Environment: "testing",
    })
    
    // Start the application
    go func() {
        app.Run()
    }()
    defer app.Stop(context.Background())
    
    // Test with real HTTP client
    resp, err := http.Get("http://localhost:8080/users")
    require.NoError(t, err)
    defer resp.Body.Close()
    
    assert.Equal(t, 200, resp.StatusCode)
}

Best Practices

  1. Route Organization: Group related routes together
  2. Middleware Order: Order middleware by performance impact
  3. Error Handling: Use appropriate HTTP status codes
  4. Validation: Validate input at route level
  5. Documentation: Use OpenAPI annotations for API docs
  6. Testing: Write comprehensive tests for all routes
  7. Performance: Profile and optimize critical routes
  8. Security: Apply authentication and authorization appropriately

For more advanced routing features, see the OpenAPI documentation and Authentication guide.

How is this guide?

Last updated on