Routing System

Overview

Weber's routing system maps HTTP requests to handler functions. It supports various HTTP methods, URL parameters, and middleware integration.

Basic Routing

Defining Routes

import "weber/backend/app"

func SetupRoutes(a *app.App) {
    // GET request
    a.GET("/", IndexHandler)
    
    // POST request
    a.POST("/submit", SubmitHandler)
    
    // Multiple methods
    a.Handle("/contact", ContactHandler, "GET", "POST")
}

Route Handlers

func IndexHandler(ctx *app.Context) {
    ctx.Render("index.html", map[string]interface{}{
        "title": "Home Page",
    })
}

func SubmitHandler(ctx *app.Context) {
    // Process form data
    data := ctx.PostForm()
    
    // Return JSON response
    ctx.JSON(200, map[string]interface{}{
        "success": true,
        "message": "Data submitted successfully",
    })
}

URL Parameters

Dynamic Routes

// Single parameter
a.GET("/news/:id", NewsDetailHandler)

// Multiple parameters
a.GET("/category/:category/post/:id", PostHandler)

// Optional segments
a.GET("/search/*query", SearchHandler)

Accessing Parameters

func NewsDetailHandler(ctx *app.Context) {
    // Get URL parameter
    id := ctx.Param("id")
    
    // Fetch news article
    article, err := model.GetNewsById(id)
    if err != nil {
        ctx.NotFound()
        return
    }
    
    ctx.Render("news-detail.html", map[string]interface{}{
        "article": article,
    })
}

func PostHandler(ctx *app.Context) {
    category := ctx.Param("category")
    id := ctx.Param("id")
    
    // Use parameters...
}

Query Parameters

Reading Query Strings

// URL: /search?q=golang&page=2&sort=date

func SearchHandler(ctx *app.Context) {
    // Get single query parameter
    query := ctx.Query("q")
    
    // Get with default value
    page := ctx.QueryDefault("page", "1")
    
    // Get all values for a parameter
    tags := ctx.QueryArray("tag")
    
    // Convert to int
    pageNum, _ := strconv.Atoi(page)
    
    // Use parameters...
}

Request Data

Form Data

func FormHandler(ctx *app.Context) {
    // Get form value
    name := ctx.PostForm("name")
    email := ctx.PostForm("email")
    
    // Get all form data
    form := ctx.PostFormAll()
    
    // File upload
    file, header, err := ctx.FormFile("avatar")
    if err == nil {
        defer file.Close()
        // Process file...
    }
}

JSON Body

type RequestData struct {
    Name  string `json:"name"`
    Email string `json:"email"`
    Age   int    `json:"age"`
}

func APIHandler(ctx *app.Context) {
    var data RequestData
    
    // Bind JSON body to struct
    if err := ctx.BindJSON(&data); err != nil {
        ctx.Error(400, "Invalid JSON")
        return
    }
    
    // Use data...
    ctx.JSON(200, map[string]interface{}{
        "received": data,
    })
}

Route Groups

Organizing Routes

func SetupRoutes(a *app.App) {
    // Public routes
    a.GET("/", IndexHandler)
    a.GET("/about", AboutHandler)
    
    // News routes
    newsGroup := a.Group("/news")
    {
        newsGroup.GET("", NewsListHandler)
        newsGroup.GET("/:id", NewsDetailHandler)
        newsGroup.GET("/category/:cat", NewsCategoryHandler)
    }
    
    // API routes
    apiGroup := a.Group("/api")
    {
        apiGroup.GET("/news", APINewsHandler)
        apiGroup.GET("/football", APIFootballHandler)
        apiGroup.POST("/submit", APISubmitHandler)
    }
    
    // Admin routes (with auth middleware)
    adminGroup := a.Group("/admin", AuthMiddleware)
    {
        adminGroup.GET("/dashboard", AdminDashboardHandler)
        adminGroup.POST("/publish", AdminPublishHandler)
    }
}

Middleware

Global Middleware

func SetupMiddleware(a *app.App) {
    // Apply to all routes
    a.Use(LoggerMiddleware)
    a.Use(RecoveryMiddleware)
    a.Use(CORSMiddleware)
}

Creating Middleware

func LoggerMiddleware(next app.HandlerFunc) app.HandlerFunc {
    return func(ctx *app.Context) {
        start := time.Now()
        path := ctx.Path()
        method := ctx.Method()
        
        // Process request
        next(ctx)
        
        // Log after request
        duration := time.Since(start)
        log.Printf("%s %s - %v", method, path, duration)
    }
}

func AuthMiddleware(next app.HandlerFunc) app.HandlerFunc {
    return func(ctx *app.Context) {
        // Check authentication
        token := ctx.GetHeader("Authorization")
        if !isValidToken(token) {
            ctx.Error(401, "Unauthorized")
            return
        }
        
        // Continue to next handler
        next(ctx)
    }
}

Route-Specific Middleware

// Apply to specific routes
a.GET("/protected", ProtectedHandler, AuthMiddleware, RateLimitMiddleware)

// Apply to route group
adminGroup := a.Group("/admin", AuthMiddleware, AdminCheckMiddleware)

Response Types

HTML Response

func Handler(ctx *app.Context) {
    ctx.Render("template.html", data)
}

JSON Response

func APIHandler(ctx *app.Context) {
    ctx.JSON(200, map[string]interface{}{
        "status": "success",
        "data": data,
    })
}

Plain Text

func TextHandler(ctx *app.Context) {
    ctx.Text(200, "Hello, World!")
}

Redirect

func RedirectHandler(ctx *app.Context) {
    ctx.Redirect(302, "/new-location")
}

File Download

func DownloadHandler(ctx *app.Context) {
    ctx.File("/path/to/file.pdf")
    // or
    ctx.FileAttachment("/path/to/file.pdf", "download.pdf")
}

Custom Response

func CustomHandler(ctx *app.Context) {
    ctx.SetHeader("Content-Type", "application/xml")
    ctx.SetStatus(200)
    ctx.Write([]byte("<xml>...</xml>"))
}

Error Handling

Built-in Error Responses

func Handler(ctx *app.Context) {
    // 404 Not Found
    ctx.NotFound()
    
    // 400 Bad Request
    ctx.BadRequest("Invalid input")
    
    // 500 Internal Server Error
    ctx.InternalServerError("Something went wrong")
    
    // Custom error
    ctx.Error(403, "Forbidden")
}

Custom Error Pages

func Setup404Handler(a *app.App) {
    a.NoRoute(func(ctx *app.Context) {
        ctx.Render("404.html", map[string]interface{}{
            "path": ctx.Path(),
        })
    })
}

func Setup500Handler(a *app.App) {
    a.OnError(func(ctx *app.Context, err error) {
        log.Error("Server error:", err)
        ctx.Render("500.html", map[string]interface{}{
            "message": "Internal Server Error",
        })
    })
}

Static Files

Serving Static Assets

func SetupStatic(a *app.App) {
    // Serve directory
    a.Static("/static", "./webroot/static")
    
    // Serve single file
    a.StaticFile("/favicon.ico", "./webroot/favicon.ico")
    
    // Serve with custom handler
    a.GET("/assets/*filepath", func(ctx *app.Context) {
        filepath := ctx.Param("filepath")
        ctx.File("./assets/" + filepath)
    })
}

Request Context

Context Methods

func Handler(ctx *app.Context) {
    // Request info
    method := ctx.Method()      // GET, POST, etc.
    path := ctx.Path()          // /news/123
    host := ctx.Host()          // example.com
    
    // Headers
    userAgent := ctx.GetHeader("User-Agent")
    ctx.SetHeader("X-Custom", "value")
    
    // Cookies
    value := ctx.Cookie("session")
    ctx.SetCookie("session", "value", 3600)
    
    // Client IP
    ip := ctx.ClientIP()
    
    // Store data in context
    ctx.Set("user", user)
    user := ctx.Get("user")
}

Route Organization

Modular Route Files

// backend/route/news.go
package route

func SetupNewsRoutes(a *app.App) {
    news := a.Group("/news")
    news.GET("", ListHandler)
    news.GET("/:id", DetailHandler)
    news.GET("/category/:cat", CategoryHandler)
}

// backend/route/football.go
func SetupFootballRoutes(a *app.App) {
    football := a.Group("/football")
    football.GET("", ListHandler)
    football.GET("/:id", DetailHandler)
}

// backend/route/route.go
func Setup(a *app.App) {
    SetupNewsRoutes(a)
    SetupFootballRoutes(a)
    SetupAPIRoutes(a)
}

Advanced Routing

Regular Expression Routes

// Match numeric IDs only
a.GET("/news/:id([0-9]+)", NewsHandler)

// Match slugs
a.GET("/post/:slug([a-z0-9-]+)", PostHandler)

// Match year/month/day
a.GET("/archive/:year([0-9]{4})/:month([0-9]{2})", ArchiveHandler)

Wildcard Routes

// Match everything after /files/
a.GET("/files/*filepath", FileHandler)

func FileHandler(ctx *app.Context) {
    filepath := ctx.Param("filepath")
    // filepath contains: /path/to/file.txt
}

Method-Specific Handlers

// Same path, different methods
a.GET("/resource", GetResource)
a.POST("/resource", CreateResource)
a.PUT("/resource/:id", UpdateResource)
a.DELETE("/resource/:id", DeleteResource)
a.PATCH("/resource/:id", PatchResource)

Best Practices

  • RESTful Design - Follow REST conventions for API routes
  • Consistent Naming - Use clear, consistent route naming
  • Route Grouping - Organize related routes into groups
  • Middleware Order - Apply middleware in logical order
  • Error Handling - Always handle errors gracefully
  • Input Validation - Validate all input parameters
  • Security - Implement authentication and authorization
  • Documentation - Document your routes and their parameters

Example: Complete Route Setup

package route

import "weber/backend/app"

func Setup(a *app.App) {
    // Global middleware
    a.Use(LoggerMiddleware)
    a.Use(RecoveryMiddleware)
    a.Use(CORSMiddleware)
    
    // Static files
    a.Static("/static", "./webroot/static")
    
    // Public pages
    a.GET("/", IndexHandler)
    a.GET("/about", AboutHandler)
    a.GET("/contact", ContactHandler)
    a.POST("/contact", ContactSubmitHandler)
    
    // News section
    news := a.Group("/news")
    {
        news.GET("", NewsListHandler)
        news.GET("/:id", NewsDetailHandler)
        news.GET("/category/:category", NewsCategoryHandler)
        news.GET("/more", NewsMoreHandler)
    }
    
    // Football section
    football := a.Group("/football")
    {
        football.GET("", FootballHandler)
        football.GET("/:slug", FootballDetailHandler)
    }
    
    // API routes
    api := a.Group("/api", RateLimitMiddleware)
    {
        api.GET("/news", APINewsHandler)
        api.GET("/football", APIFootballHandler)
        api.POST("/search", APISearchHandler)
    }
    
    // Admin routes (protected)
    admin := a.Group("/admin", AuthMiddleware)
    {
        admin.GET("/dashboard", AdminDashboardHandler)
        admin.GET("/content", AdminContentHandler)
        admin.POST("/publish", AdminPublishHandler)
        admin.DELETE("/content/:id", AdminDeleteHandler)
    }
    
    // Custom 404
    a.NoRoute(NotFoundHandler)
}

Next Steps