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
- Route Organization: Group related routes together
- Middleware Order: Order middleware by performance impact
- Error Handling: Use appropriate HTTP status codes
- Validation: Validate input at route level
- Documentation: Use OpenAPI annotations for API docs
- Testing: Write comprehensive tests for all routes
- Performance: Profile and optimize critical routes
- 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