Routing

This documentation explains how to handle HTTP requests in Confetti Framework using standard Go techniques and the built-in routing system with handler.New(). This provides full control over routes and middleware without external libraries.

Defining Routes

Routes are defined using handler.New("METHOD PATH", handlerFunc). Middleware can be added via .AppendMiddleware().

Example:

var ApiRoutes = []handler.Route{
	handler.New("GET /ping", ping.Index),
	handler.New("GET /status", status.Index).AppendMiddleware(middleware.AuthMiddleware{Permission: "Show status"}),
}

The GET /ping route is added without middleware, while the GET /status route requires authentication with the appropriate permission.

Controller Structure

Controllers must use the following function signature:

func Index(response http.ResponseWriter, request *http.Request) error {
    // Processing logic here
    return nil
}

Parameters:

  • response: The http.ResponseWriter used to write the response.
  • request: The *http.Request containing the incoming request.
  • error: Returns an error if a problem occurs.

Enhanced Routing Patterns

The Go 1.22+ routing system allows for more expressive patterns:

Method-Specific Routes

Routes can be defined with HTTP methods for stricter matching. For example, POST /items/create ensures only POST requests are allowed for that route.

Wildcard Parameters

  • {param} captures a single path segment and can be retrieved using request.PathValue("param").
  • {param...} matches multiple segments at the end of a path.

Example:

func ShowFile(response http.ResponseWriter, request *http.Request) error {
    filePath := request.PathValue("path")
    fmt.Fprintf(response, "File path: %s", filePath)
    return nil
}

For the route GET /files/{path...}, path captures all remaining segments.

Exact Matches

To ensure an exact match with a trailing slash, use {$}:

handler.New("GET /users/{$}", user.List)

Working with Route Parameters

Example:

func ShowUser(response http.ResponseWriter, request *http.Request) error {
    userID := request.PathValue("id")
    fmt.Fprintf(response, "User ID: %s", userID)
    return nil
}

For a route like GET /users/{id}, the {id} value is retrieved using request.PathValue("id").

Query Parameters

Query parameters can be extracted directly from the *http.Request:

func Search(response http.ResponseWriter, request *http.Request) error {
    query := request.URL.Query().Get("q")
    fmt.Fprintf(response, "Search results for: %s", query)
    return nil
}

Redirects

To implement a redirect in an idiomatic Go way:

func RedirectToHome(response http.ResponseWriter, request *http.Request) error {
    http.Redirect(response, request, "/home", http.StatusFound)
    return nil
}

Regular Expression Constraints

Regular expressions can be used in routes to validate parameters. Example:

var ApiRoutes = []handler.Route{
    handler.New("GET /product/{id:[0-9]+}", product.Show),
}

Here, {id} is restricted to numeric values ([0-9]+).

Form Data

To process form submissions, first parse the form data:

func ProcessForm(response http.ResponseWriter, request *http.Request) error {
    request.ParseForm()
    name := request.Form.Get("name")
    fmt.Fprintf(response, "Received name: %s", name)
    return nil
}

Raw Request Body

To read the raw request body:

import "io"

func ReadBody(response http.ResponseWriter, request *http.Request) error {
    bodyBytes, err := io.ReadAll(request.Body)
    if err != nil {
        http.Error(response, "Error reading body", http.StatusInternalServerError)
        return err
    }
    bodyString := string(bodyBytes)
    fmt.Fprintf(response, "Request body: %s", bodyString)
    return nil
}

Handling Cookies

Retrieve cookies using:

func GetSessionCookie(response http.ResponseWriter, request *http.Request) error {
    cookie, err := request.Cookie("session_id")
    if err != nil {
        http.Error(response, "Session cookie missing", http.StatusUnauthorized)
        return err
    }
    fmt.Fprintf(response, "Session ID: %s", cookie.Value)
    return nil
}

Handling File Uploads

For file uploads, use request.FormFile():

func UploadFile(response http.ResponseWriter, request *http.Request) error {
    file, header, err := request.FormFile("photo")
    if err != nil {
        http.Error(response, "Error retrieving file", http.StatusBadRequest)
        return err
    }
    defer file.Close()
    fmt.Fprintf(response, "Uploaded file: %s", header.Filename)
    return nil
}
Contributors: reindert-vetter, Reindert Vetter, Vaggelis Yfantis