Routing & Handlers

irgo uses a chi-based router with a simplified API designed for HTMX applications. Handlers return HTML strings rather than writing to response writers.

Basic Routing

import "github.com/stukennedy/irgo/pkg/router"r := router.New()// Basic routesr.GET("/", HomeHandler)r.POST("/users", CreateUserHandler)r.PUT("/users/{id}", UpdateUserHandler)r.DELETE("/users/{id}", DeleteUserHandler)r.PATCH("/users/{id}", PatchUserHandler)

Fragment Handlers

Handlers in irgo return (string, error) instead of writing tohttp.ResponseWriter. This makes them cleaner and easier to test:

func HomeHandler(ctx *router.Context) (string, error) {    return renderer.Render(templates.HomePage())}func UserHandler(ctx *router.Context) (string, error) {    id := ctx.Param("id")    user, err := db.GetUser(id)    if err != nil {        return "", ctx.NotFound("User not found")    }    return renderer.Render(templates.UserProfile(user))}

URL Parameters

Use curly braces for URL parameters:

// Single parameterr.GET("/users/{id}", func(ctx *router.Context) (string, error) {    id := ctx.Param("id")    return fmt.Sprintf("User ID: %s", id), nil})// Multiple parametersr.GET("/posts/{postID}/comments/{commentID}", func(ctx *router.Context) (string, error) {    postID := ctx.Param("postID")    commentID := ctx.Param("commentID")    return fmt.Sprintf("Post %s, Comment %s", postID, commentID), nil})

Query Parameters

// GET /search?q=hello&limit=10r.GET("/search", func(ctx *router.Context) (string, error) {    query := ctx.Query("q")    limit := ctx.Query("limit")    if query == "" {        return "", ctx.BadRequest("Query parameter 'q' is required")    }    results := search(query, limit)    return renderer.Render(templates.SearchResults(results))})

Form Data

r.POST("/users", func(ctx *router.Context) (string, error) {    name := ctx.FormValue("name")    email := ctx.FormValue("email")    if name == "" || email == "" {        return "", ctx.BadRequest("Name and email are required")    }    user := db.CreateUser(name, email)    return renderer.Render(templates.UserCard(user))})

Route Groups

Organize related routes with groups:

r.Route("/api", func(r *router.Router) {    // All routes here are prefixed with /api    r.GET("/users", ListUsers)    r.POST("/users", CreateUser)    r.Route("/admin", func(r *router.Router) {        // /api/admin/...        r.GET("/stats", AdminStats)        r.POST("/reset", AdminReset)    })})

Context API

The router.Context provides methods for handling requests and responses:

Input Methods

func handler(ctx *router.Context) (string, error) {    // URL parameters    id := ctx.Param("id")    // Query string parameters    query := ctx.Query("q")    // Form values    name := ctx.FormValue("name")    // Request headers    token := ctx.Header("Authorization")    // Access underlying request    req := ctx.Request    return "", nil}

HTMX Detection

func handler(ctx *router.Context) (string, error) {    // Check if request is from HTMX    if ctx.IsHTMX() {        // Return partial HTML        return renderer.Render(templates.UserCard(user))    }    // Return full page    return renderer.Render(templates.UserPage(user))}// HTMX request headerstarget := ctx.HXTarget()       // HX-Target headertrigger := ctx.HXTrigger()     // HX-Trigger headercurrentURL := ctx.HXCurrentURL() // HX-Current-URL header

Response Methods

// HTML responsesctx.HTML("<div>Hello</div>")ctx.HTMLStatus(201, "<div>Created</div>")// JSON responsesctx.JSON(user)ctx.JSONStatus(201, user)// Redirects (HTMX-aware)ctx.Redirect("/new-location")// No contentctx.NoContent()

Error Responses

// Generic errorctx.Error(err)ctx.ErrorStatus(500, "Internal error")// Common HTTP errorsctx.NotFound("Resource not found")ctx.BadRequest("Invalid input")ctx.Unauthorized("Please log in")ctx.Forbidden("Access denied")

HTMX Response Headers

Control HTMX behavior with response headers:

func handler(ctx *router.Context) (string, error) {    // Update browser URL    ctx.PushURL("/users/123")    ctx.ReplaceURL("/users/123")    // Trigger client-side events    ctx.Trigger("userCreated")    ctx.TriggerAfterSettle("formReset")    ctx.TriggerAfterSwap("showNotification")    // Change swap behavior    ctx.Retarget("#different-element")    ctx.Reswap("innerHTML")    // Force full page refresh    ctx.Refresh()    return renderer.Render(templates.UserCard(user))}
Out-of-Band Swaps

Use hx-swap-oob="true" in your templates to update multiple elements with a single response. This is great for updating counters, notifications, or other elements outside the main target.

Static Files

// Serve static files from a directoryr.Static("/static", http.Dir("static"))// Or use http.FileServer directlymux := http.NewServeMux()mux.Handle("/static/", http.StripPrefix("/static/",    http.FileServer(http.Dir("static"))))mux.Handle("/", r.Handler())

Middleware

irgo includes common middleware:

import "github.com/stukennedy/irgo/pkg/router/middleware"r := router.New()// Detect HTMX requests (sets ctx.IsHTMX())r.Use(middleware.HXRequestMiddleware)// Wrap non-HTMX responses in layoutr.Use(middleware.LayoutWrapper(templates.Layout))// Prevent cachingr.Use(middleware.NoCacheMiddleware)// CORS supportr.Use(middleware.CORSMiddleware)// Require HTMX for routesr.Route("/fragments", func(r *router.Router) {    r.Use(middleware.RequireHTMX)    r.GET("/user-card", UserCardFragment)})

Getting the HTTP Handler

Convert the router to a standard http.Handler:

r := router.New()// ... register routes ...// Get the http.Handlerhandler := r.Handler()// Use with http.ListenAndServehttp.ListenAndServe(":8080", handler)// Or with desktop.Newapp := desktop.New(handler, config)

Next Steps