Router
Zero-dependency HTTP router with radix tree routing, middleware chaining, and high-performance request handling
Overview
Base Framework's router is a lightweight, zero-dependency HTTP router built with radix tree routing for optimal performance. It features middleware chaining, context pooling, and comprehensive request/response handling.
Key Features
- Radix Tree Routing - Efficient URL pattern matching with parameter extraction and wildcard support
- Middleware Chaining - Composable middleware with global and route-specific support for cross-cutting concerns
- Context Pooling - Memory-efficient context reuse with zero-allocation routing for high-throughput applications
Route Definition & Registration
Basic Route Registration
go
r := router.New()
// Basic HTTP methods
r.GET("/users", handleGetUsers)
r.POST("/users", handleCreateUser)
r.PUT("/users/:id", handleUpdateUser)
r.DELETE("/users/:id", handleDeleteUser)
r.PATCH("/users/:id", handlePatchUser)
// Generic handler for any HTTP method
r.Handle("GET", "/custom", handleCustom)
Route Groups
go
// API v1 group
api := r.Group("/api/v1")
api.GET("/users", handleGetUsers)
api.POST("/users", handleCreateUser)
// Admin group with middleware
admin := r.Group("/admin", AuthMiddleware(), AdminMiddleware())
admin.GET("/users", handleAdminGetUsers)
admin.DELETE("/users/:id", handleAdminDeleteUser)
// Nested groups
v2 := api.Group("/v2")
v2.GET("/posts", handleGetPostsV2)
// Group with multiple middleware
protected := r.Group("/protected",
middleware.Logger(loggerConfig),
middleware.Auth(authConfig),
middleware.RateLimit(rateLimitConfig),
)
protected.GET("/profile", handleProfile)
Context API & Request Handling
Basic Handler Function
go
func handleUser(c *router.Context) error {
// Access request information
method := c.Request.Method
path := c.Request.URL.Path
userAgent := c.Request.UserAgent()
clientIP := c.ClientIP()
// Get headers
authHeader := c.Header("Authorization")
contentType := c.ContentType()
// Store data in context for other middleware/handlers
c.Set("user_id", 123)
c.Set("request_start", time.Now())
// Retrieve stored data
userID, exists := c.Get("user_id")
if exists {
c.Logger.Info("Processing for user", "id", userID)
}
return c.JSON(200, map[string]any{
"message": "Success",
"ip": clientIP,
"method": method,
})
}
Route Parameters & Query Strings
URL Parameters
go
// Route with parameters
r.GET("/users/:id/posts/:postId", func(c *router.Context) error {
// Named parameters
userID := c.Param("id")
postID := c.Param("postId")
return c.JSON(200, map[string]string{
"user_id": userID,
"post_id": postID,
})
})
// Wildcard routes
r.GET("/files/*filepath", func(c *router.Context) error {
filepath := c.Param("filepath")
// filepath captures everything after /files/
return c.File("./static/" + filepath)
})
// Multiple parameters with validation
r.GET("/api/v1/users/:id/orders/:orderNumber", func(c *router.Context) error {
userID := c.Param("id")
orderNumber := c.Param("orderNumber")
// Convert to appropriate types
id, err := strconv.Atoi(userID)
if err != nil {
return c.JSON(400, map[string]string{"error": "Invalid user ID"})
}
return handleOrder(c, id, orderNumber)
})
Query Parameters
go
func handleSearch(c *router.Context) error {
// Single query parameter
query := c.Query("q")
page := c.DefaultQuery("page", "1")
// Check if parameter exists
category, exists := c.GetQuery("category")
if !exists {
category = "all"
}
// Multiple values for same parameter
tags, hasTag := c.GetQueryArray("tags")
if hasTag {
// Process array of tags: ?tags=go&tags=web&tags=api
c.Logger.Info("Tags", "values", tags)
}
// Convert query parameters
limitStr := c.DefaultQuery("limit", "10")
limit, err := strconv.Atoi(limitStr)
if err != nil {
return c.JSON(400, map[string]string{
"error": "Invalid limit parameter",
})
}
return c.JSON(200, map[string]any{
"query": query,
"page": page,
"category": category,
"tags": tags,
"limit": limit,
})
}
Request Binding & Validation
JSON & Form Binding
go
type CreateUserRequest struct {
Name string `json:"name" form:"name" validate:"required,min=2"`
Email string `json:"email" form:"email" validate:"required,email"`
Age int `json:"age" form:"age" validate:"min=18,max=100"`
IsActive bool `json:"is_active" form:"is_active"`
}
func createUser(c *router.Context) error {
var req CreateUserRequest
// Auto-detects content type and binds accordingly
if err := c.Bind(&req); err != nil {
return c.JSON(400, map[string]string{
"error": "Invalid request format",
"details": err.Error(),
})
}
// Manual binding for specific content types
var jsonReq CreateUserRequest
if err := c.BindJSON(&jsonReq); err != nil {
return c.JSON(400, map[string]string{"error": "Invalid JSON"})
}
// Form binding
var formReq CreateUserRequest
if err := c.BindForm(&formReq); err != nil {
return c.JSON(400, map[string]string{"error": "Invalid form data"})
}
// Query parameter binding
var queryReq CreateUserRequest
if err := c.BindQuery(&queryReq); err != nil {
return c.JSON(400, map[string]string{"error": "Invalid query params"})
}
return c.JSON(201, req)
}
File Upload Handling
go
func uploadFile(c *router.Context) error {
// Single file upload
file, err := c.FormFile("upload")
if err != nil {
return c.JSON(400, map[string]string{
"error": "No file provided",
})
}
// Access file metadata
filename := file.Filename
size := file.Size
contentType := file.Header.Get("Content-Type")
// Process multipart form
form, err := c.MultipartForm()
if err != nil {
return c.JSON(400, map[string]string{
"error": "Invalid multipart form",
})
}
// Handle multiple files
files := form.File["files"]
for _, fileHeader := range files {
// Process each file
file, err := fileHeader.Open()
if err != nil {
continue
}
defer file.Close()
// Save file logic here
c.Logger.Info("Processing file", "name", fileHeader.Filename)
}
// Get form values from multipart form
title := c.FormValue("title")
description := c.FormValue("description")
return c.JSON(200, map[string]any{
"message": "Files uploaded successfully",
"filename": filename,
"size": size,
"content_type": contentType,
"title": title,
})
}
Response Methods & Content Types
JSON & Data Responses
go
func handleResponses(c *router.Context) error {
// JSON response
return c.JSON(200, map[string]any{
"message": "Success",
"data": []string{"item1", "item2"},
"count": 2,
})
// String response
return c.String(200, "Plain text response")
// HTML response
html := `<h1>Hello World</h1><p>This is HTML content</p>`
return c.HTML(200, html)
// Raw data response
data := []byte("binary data here")
return c.Data(200, "application/octet-stream", data)
// File response
c.File("/path/to/file.pdf")
// No content response
return c.NoContent()
// Custom headers with response
c.SetHeader("X-Custom-Header", "custom-value")
c.SetHeader("Cache-Control", "max-age=3600")
return c.JSON(200, map[string]string{"status": "ok"})
}
// Error response helper
func handleError(c *router.Context) error {
err := errors.New("something went wrong")
return c.Error(500, err) // Automatically formats as JSON error
}
// Redirect responses
func handleRedirect(c *router.Context) error {
return c.Redirect(302, "https://example.com")
}
Middleware Usage & Custom Middleware
Built-in Middleware
go
import (
"base/core/router"
"base/core/router/middleware"
"base/core/logger"
)
func setupRouter() *router.Router {
r := router.New()
log := logger.New()
// Global middleware (applied to all routes)
r.Use(middleware.Recovery(log))
r.Use(middleware.RequestID())
r.Use(middleware.Logger(middleware.DefaultLoggerConfig(log)))
r.Use(middleware.RateLimit(middleware.DefaultRateLimitConfig()))
// Authentication middleware
authConfig := middleware.DefaultAuthConfig()
authConfig.TokenValidator = validateJWTToken
authConfig.SkipPaths = []string{"/login", "/register", "/health"}
// Public routes
r.GET("/health", handleHealth)
r.POST("/login", handleLogin)
r.POST("/register", handleRegister)
// Protected routes group
api := r.Group("/api/v1", middleware.Auth(authConfig))
api.GET("/profile", handleProfile)
api.PUT("/profile", handleUpdateProfile)
// Admin routes with additional middleware
admin := r.Group("/admin",
middleware.Auth(authConfig),
middleware.RequireAuth("user"),
adminMiddleware,
)
admin.GET("/users", handleGetUsers)
admin.DELETE("/users/:id", handleDeleteUser)
return r
}
Custom Middleware
go
// Custom CORS middleware
func CORSMiddleware() router.MiddlewareFunc {
return func(next router.HandlerFunc) router.HandlerFunc {
return func(c *router.Context) error {
// Set CORS headers
c.SetHeader("Access-Control-Allow-Origin", "*")
c.SetHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
c.SetHeader("Access-Control-Allow-Headers", "Content-Type, Authorization")
// Handle preflight requests
if c.Request.Method == "OPTIONS" {
return c.NoContent()
}
return next(c)
}
}
}
// Timing middleware
func TimingMiddleware() router.MiddlewareFunc {
return func(next router.HandlerFunc) router.HandlerFunc {
return func(c *router.Context) error {
start := time.Now()
// Process request
err := next(c)
// Add timing header
duration := time.Since(start)
c.SetHeader("X-Response-Time", duration.String())
return err
}
}
}
// Role-based access control middleware
func RequireRole(role string) router.MiddlewareFunc {
return func(next router.HandlerFunc) router.HandlerFunc {
return func(c *router.Context) error {
user, exists := c.Get("user")
if !exists {
return c.JSON(401, map[string]string{
"error": "Authentication required",
})
}
userRole := getUserRole(user)
if userRole != role && userRole != "admin" {
return c.JSON(403, map[string]string{
"error": "Insufficient permissions",
})
}
return next(c)
}
}
}
// API versioning middleware
func APIVersionMiddleware() router.MiddlewareFunc {
return func(next router.HandlerFunc) router.HandlerFunc {
return func(c *router.Context) error {
version := c.Header("API-Version")
if version == "" {
version = "v1" // default
}
c.Set("api_version", version)
c.SetHeader("API-Version", version)
return next(c)
}
}
}
Error Handling & HTTP Status Codes
Error Responses & Recovery
go
// Custom error types
type APIError struct {
Code int `json:"code"`
Message string `json:"message"`
Details any `json:"details,omitempty"`
}
func (e APIError) Error() string {
return e.Message
}
// Error handler with detailed responses
func handleUserCreate(c *router.Context) error {
var req CreateUserRequest
if err := c.Bind(&req); err != nil {
return c.JSON(400, APIError{
Code: 40001,
Message: "Invalid request format",
Details: err.Error(),
})
}
// Validation
if req.Email == "" {
return c.JSON(400, APIError{
Code: 40002,
Message: "Email is required",
})
}
user, err := userService.Create(req)
if err != nil {
if errors.Is(err, ErrUserExists) {
return c.JSON(409, APIError{
Code: 40901,
Message: "User already exists",
})
}
// Log internal errors but don't expose details
c.Logger.Error("Failed to create user", "error", err)
return c.JSON(500, APIError{
Code: 50001,
Message: "Internal server error",
})
}
return c.JSON(201, user)
}
// Global error handler
func ErrorHandler(c *router.Context, err error) {
var apiErr APIError
switch e := err.(type) {
case APIError:
apiErr = e
case *validation.ValidationError:
apiErr = APIError{
Code: 40003,
Message: "Validation failed",
Details: e.Fields,
}
default:
apiErr = APIError{
Code: 50000,
Message: "Internal server error",
}
}
c.JSON(apiErr.Code/100, apiErr)
}
// Custom 404 handler
func notFoundHandler(c *router.Context) error {
return c.JSON(404, APIError{
Code: 40401,
Message: "Endpoint not found",
Details: map[string]string{
"path": c.Request.URL.Path,
"method": c.Request.Method,
},
})
}
// Setup router with error handling
func setupErrorHandling() {
r := router.New()
// Set custom 404 handler
r.NotFound(notFoundHandler)
// Recovery middleware
r.Use(middleware.Recovery(logger))
}
Performance Considerations & Best Practices
Performance Features
- Zero-allocation radix tree routing
- Context pooling for memory efficiency
- Fast parameter extraction
- Efficient middleware chaining
- Thread-safe operations
Benchmarks
- Route lookup: ~50ns
- Context creation: ~0 allocs
- Parameter extraction: ~30ns
- Memory per request: ~0.5KB
- Throughput: 100K+ RPS
Performance Best Practices
Do's:
- Use route groups to minimize middleware overhead
- Place more specific routes before wildcard routes
- Minimize middleware chain length for critical paths
- Use context pooling (automatic in Base router)
- Implement proper error handling to avoid panics
- Use built-in response methods for better performance
Don'ts:
- Don't create new context instances manually
- Avoid heavy computation in middleware for all routes
- Don't ignore context cancellation signals
- Avoid deep middleware nesting without caching
- Don't use blocking operations in hot paths
- Avoid large response payloads without streaming
Static File Serving & Advanced Features
Static File Configuration
go
// Basic static file serving
r.Static("/static", "./public")
r.Static("/uploads", "./storage/uploads")
// Group-specific static files
api := r.Group("/api/v1")
api.Static("/assets", "./api-assets")
// Custom file handler with middleware
r.GET("/files/*filepath", middleware.Auth(authConfig), func(c *router.Context) error {
filepath := c.Param("filepath")
// Security check
if strings.Contains(filepath, "..") {
return c.JSON(400, map[string]string{"error": "Invalid path"})
}
fullPath := path.Join("./secure-files", filepath)
// Check file exists and user has access
if !hasFileAccess(c.MustGet("user"), fullPath) {
return c.JSON(403, map[string]string{"error": "Access denied"})
}
c.File(fullPath)
return nil
})
// Server setup with graceful shutdown
func main() {
r := router.New()
setupRoutes(r)
// Start server
if err := r.Run(":8080"); err != nil {
log.Fatal("Server failed to start:", err)
}
}