Skip to content

Routing & Mux

HTTP routing allows you to direct different URLs to different handler functions. Harneet's HTTP module provides a powerful multiplexer (mux) system based on Go's http.ServeMux for pattern-based URL routing.

ServeMux Basics

http.NewMux()

Creates a new HTTP request multiplexer for routing requests.

Syntax:

NewMux Syntax
http.NewMux() (mux, error)

Returns: - mux (map) - ServeMux object for routing - error - None on success, error on failure

Examples:

Basic Mux Creation:

Basic Mux Creation
import http
import fmt

// Create a new mux
var result = http.NewMux()
var mux = result[0]
var err = result[1]

if err != None {
    fmt.Printf("Failed to create mux: %s\n", err)
} else {
    fmt.Println("✅ Mux created successfully")
}

### Method-Aware Routing with http.HandleRoute

For common REST patterns it is convenient to bind the HTTP method and path pattern
directly when registering a route. Use `http.HandleRoute` to register method-aware
handlers with path parameters.

```harneet title="HandleRoute with Path Parameters"
import http
import fmt

function getUserHandler(request any, response any) {
    var params = request["params"]
    var id = params["id"]
    response.json({"id": id, "message": "User fetched"})
}

function setupHandleRouteMux() {
    var mux, muxErr = http.NewMux()
    if muxErr != None {
        fmt.Printf("Mux error: %s\n", muxErr)
        return None
    }

    // Optional: attach error handling, metrics, CORS, auth, and rate limiting
    var mux1, err1 = http.WithErrorHandling(mux, errorHandler)
    if err1 != None { return None }
    var mux2, err2 = http.WithMetrics(mux1, metricsHandler)
    if err2 != None { return None }
    var mux3, err3 = http.WithCORS(mux2)
    if err3 != None { return None }

    // Register a GET route with a path parameter {id}
    var _, routeErr = http.HandleRoute(mux3, "GET", "/users/{id}", getUserHandler)
    if routeErr != None {
        fmt.Printf("Route error: %s\n", routeErr)
        return None
    }

    return mux3
}

function startUserServer() {
    var server, serverErr = http.NewServer(":8080")
    if serverErr != None {
        fmt.Printf("Server error: %s\n", serverErr)
        return
    }

    var mux = setupHandleRouteMux()
    if mux == None {
        fmt.Println("Failed to set up mux")
        return
    }

    var setErr = http.SetHandler(server, mux)[1]
    if setErr != None {
        fmt.Printf("SetHandler error: %s\n", setErr)
        return
    }

    fmt.Println("User server with HandleRoute starting on :8080")
    fmt.Println("Try: curl http://localhost:8080/users/123")
    http.ListenAndServe(server)
}

When using http.HandleRoute, any mux-level helpers configured via http.WithErrorHandling, http.WithMetrics, http.WithCORS, http.WithAuth, and http.WithRateLimit are automatically applied before your route handler runs.

1
2
3
4
5
6
7
8
9
## Route Registration

### http.HandleFunc(mux, pattern, handler)

Registers a handler function for a URL pattern.

**Syntax:**
```harneet title="HandleFunc Syntax"
http.HandleFunc(mux, pattern, handler) (error)

Parameters: - mux (map) - ServeMux object - pattern (string) - URL pattern (e.g., '/api/users', '/static/') - handler (function) - Handler function with signature (request, response)

Returns: - error - None on success, error on failure

Examples:

Basic Route Registration:

Basic Route Registration
import http
import json
import fmt

// Create mux and register routes
function setupRoutes() {
    var mux = http.NewMux()[0]

    // Register different routes
    http.HandleFunc(mux, "/", homeHandler)
    http.HandleFunc(mux, "/about", aboutHandler)
    http.HandleFunc(mux, "/api/users", usersHandler)
    http.HandleFunc(mux, "/api/posts", postsHandler)
    http.HandleFunc(mux, "/health", healthHandler)

    return mux
}

// Handler functions
function homeHandler(request map, response map) {
    var html = "<h1>Welcome to Harneet Web Server!</h1>"
    html = html + "<p><a href='/about'>About</a> | <a href='/api/users'>Users API</a></p>"
    http.WriteResponse(response, 200, "text/html", html)
}

function aboutHandler(request map, response map) {
    var html = "<h1>About</h1><p>This is a Harneet web application.</p>"
    http.WriteResponse(response, 200, "text/html", html)
}

function usersHandler(request map, response map) {
    var users = [
        {"id": 1, "name": "Alice", "email": "alice@example.com"},
        {"id": 2, "name": "Bob", "email": "bob@example.com"}
    ]
    var jsonData, _ = json.Marshal(users)
    http.WriteResponse(response, 200, "application/json", jsonData)
}

function postsHandler(request map, response map) {
    var posts = [
        {"id": 1, "title": "First Post", "author": "Alice"},
        {"id": 2, "title": "Second Post", "author": "Bob"}
    ]
    var jsonData, _ = json.Marshal(posts)
    http.WriteResponse(response, 200, "application/json", jsonData)
}

function healthHandler(request map, response map) {
    var health = {"status": "healthy", "timestamp": "2024-01-01T12:00:00Z"}
    var jsonData, _ = json.Marshal(health)
    http.WriteResponse(response, 200, "application/json", jsonData)
}

// Usage
var mux = setupRoutes()
var server = http.NewServer(":8080")[0]
http.SetHandler(server, mux)
http.ListenAndServe(server)

URL Patterns

Exact Match Patterns

Exact Match Patterns
1
2
3
4
// Exact path matching
http.HandleFunc(mux, "/", homeHandler)           // Only matches "/"
http.HandleFunc(mux, "/about", aboutHandler)     // Only matches "/about"
http.HandleFunc(mux, "/contact", contactHandler) // Only matches "/contact"

Subtree Patterns

Subtree Patterns
1
2
3
4
// Subtree matching (ends with /)
http.HandleFunc(mux, "/api/", apiHandler)        // Matches "/api/*"
http.HandleFunc(mux, "/static/", staticHandler)  // Matches "/static/*"
http.HandleFunc(mux, "/admin/", adminHandler)    // Matches "/admin/*"

Pattern Priority

Pattern Priority
import http

function setupPriorityRoutes() {
    var mux = http.NewMux()[0]

    // More specific patterns take priority
    http.HandleFunc(mux, "/api/users/profile", userProfileHandler)  // Higher priority
    http.HandleFunc(mux, "/api/users/", usersSubtreeHandler)        // Lower priority
    http.HandleFunc(mux, "/api/", apiSubtreeHandler)                // Lowest priority

    return mux
}

function userProfileHandler(request map, response map) {
    http.WriteResponse(response, 200, "text/plain", "User Profile - Specific Handler")
}

function usersSubtreeHandler(request map, response map) {
    var path = request["path"]
    http.WriteResponse(response, 200, "text/plain", "Users Subtree: " + path)
}

function apiSubtreeHandler(request map, response map) {
    var path = request["path"]
    http.WriteResponse(response, 200, "text/plain", "API Subtree: " + path)
}

RESTful API Routing

Method-Based Routing

Method-Based Routing
import http
import json
import fmt

// RESTful API with method-based routing
function setupRESTAPI() {
    var mux = http.NewMux()[0]

    // User resource endpoints
    http.HandleFunc(mux, "/api/users", usersRESTHandler)
    http.HandleFunc(mux, "/api/users/", userRESTHandler)

    // Post resource endpoints  
    http.HandleFunc(mux, "/api/posts", postsRESTHandler)
    http.HandleFunc(mux, "/api/posts/", postRESTHandler)

    return mux
}

// Users collection handler
function usersRESTHandler(request map, response map) {
    var method = request["method"]

    match method {
        "GET" => getUsersList(request, response),
        "POST" => createUser(request, response),
        _ => methodNotAllowed(response, ["GET", "POST"])
    }
}

// Individual user handler
function userRESTHandler(request map, response map) {
    var method = request["method"]
    var path = request["path"]

    // Extract user ID from path (simplified)
    var userId = extractUserIdFromPath(path)

    match method {
        "GET" => getUser(userId, request, response),
        "PUT" => updateUser(userId, request, response),
        "DELETE" => deleteUser(userId, request, response),
        _ => methodNotAllowed(response, ["GET", "PUT", "DELETE"])
    }
}

// Implementation functions
function getUsersList(request map, response map) {
    var query = request["query"]
    var page = query["page"]
    var limit = query["limit"]

    // Simulate user list with pagination
    var users = [
        {"id": 1, "name": "Alice", "email": "alice@example.com"},
        {"id": 2, "name": "Bob", "email": "bob@example.com"}
    ]

    var result = {
        "users": users,
        "page": page,
        "limit": limit,
        "total": len(users)
    }

    var jsonData, _ = json.Marshal(result)
    http.WriteResponse(response, 200, "application/json", jsonData)
}

function createUser(request map, response map) {
    var body = request["body"]
    var userData, parseErr = json.Unmarshal(body)

    if parseErr != None {
        var errorResponse = {"error": "Invalid JSON"}
        var jsonError, _ = json.Marshal(errorResponse)
        http.WriteResponse(response, 400, "application/json", jsonError)
        return
    }

    // Simulate user creation
    userData["id"] = 123
    userData["created_at"] = "2024-01-01T12:00:00Z"

    var jsonData, _ = json.Marshal(userData)
    http.WriteResponse(response, 201, "application/json", jsonData)
}

function getUser(userId string, request map, response map) {
    // Simulate user lookup
    var user = {"id": userId, "name": "Alice", "email": "alice@example.com"}
    var jsonData, _ = json.Marshal(user)
    http.WriteResponse(response, 200, "application/json", jsonData)
}

function updateUser(userId string, request map, response map) {
    var body = request["body"]
    var updates, parseErr = json.Unmarshal(body)

    if parseErr != None {
        var errorResponse = {"error": "Invalid JSON"}
        var jsonError, _ = json.Marshal(errorResponse)
        http.WriteResponse(response, 400, "application/json", jsonError)
        return
    }

    // Simulate user update
    updates["id"] = userId
    updates["updated_at"] = "2024-01-01T12:00:00Z"

    var jsonData, _ = json.Marshal(updates)
    http.WriteResponse(response, 200, "application/json", jsonData)
}

function deleteUser(userId string, request map, response map) {
    // Simulate user deletion
    var result = {"message": "User deleted", "id": userId}
    var jsonData, _ = json.Marshal(result)
    http.WriteResponse(response, 200, "application/json", jsonData)
}

function methodNotAllowed(response map, allowedMethods array) {
    var allowedStr = strings.Join(allowedMethods, ", ")
    http.SetResponseHeader(response, "Allow", allowedStr)

    var errorResponse = {"error": "Method not allowed", "allowed": allowedMethods}
    var jsonError, _ = json.Marshal(errorResponse)
    http.WriteResponse(response, 405, "application/json", jsonError)
}

function extractUserIdFromPath(path string) {
    // Extract user ID from path like "/api/users/123"
    var parts = strings.Split(path, "/")
    if len(parts) >= 4 {
        return parts[3]
    }
    return ""
}

Static File Serving

http.FileServer(root)

Creates a file server handler for serving static files.

Syntax:

FileServer Syntax
http.FileServer(root) (handler, error)

Parameters: - root (string) - Root directory path for static files

Examples:

Static File Server:

Static File Server
import http
import fmt

function setupStaticServer() {
    var mux = http.NewMux()[0]

    // Serve static files from ./public directory
    var fileServer = http.FileServer("./public")[0]
    http.Handle(mux, "/static/", http.StripPrefix("/static/", fileServer))

    // Serve favicon
    http.HandleFunc(mux, "/favicon.ico", faviconHandler)

    // API routes
    http.HandleFunc(mux, "/api/", apiHandler)

    // Default route
    http.HandleFunc(mux, "/", indexHandler)

    return mux
}

function faviconHandler(request map, response map) {
    // Serve favicon from static files
    var faviconPath = "./public/favicon.ico"
    var content = readFile(faviconPath)  // Simplified file reading
    http.WriteResponse(response, 200, "image/x-icon", content)
}

function indexHandler(request map, response map) {
    var html = `
    <!DOCTYPE html>
    <html>
    <head>
        <title>Harneet Web App</title>
        <link rel="stylesheet" href="/static/css/style.css">
    </head>
    <body>
        <h1>Welcome to Harneet!</h1>
        <script src="/static/js/app.js"></script>
    </body>
    </html>
    `
    http.WriteResponse(response, 200, "text/html", html)
}

Route Groups and Subrouters

Creating Route Groups

Route Groups
import http

function setupRouteGroups() {
    var mainMux = http.NewMux()[0]

    // API v1 routes
    var apiV1Mux = http.NewMux()[0]
    setupAPIv1Routes(apiV1Mux)
    http.Handle(mainMux, "/api/v1/", http.StripPrefix("/api/v1", apiV1Mux))

    // API v2 routes
    var apiV2Mux = http.NewMux()[0]
    setupAPIv2Routes(apiV2Mux)
    http.Handle(mainMux, "/api/v2/", http.StripPrefix("/api/v2", apiV2Mux))

    // Admin routes
    var adminMux = http.NewMux()[0]
    setupAdminRoutes(adminMux)
    http.Handle(mainMux, "/admin/", http.StripPrefix("/admin", adminMux))

    return mainMux
}

function setupAPIv1Routes(mux map) {
    http.HandleFunc(mux, "/users", v1UsersHandler)
    http.HandleFunc(mux, "/posts", v1PostsHandler)
}

function setupAPIv2Routes(mux map) {
    http.HandleFunc(mux, "/users", v2UsersHandler)
    http.HandleFunc(mux, "/posts", v2PostsHandler)
    http.HandleFunc(mux, "/comments", v2CommentsHandler)  // New in v2
}

function setupAdminRoutes(mux map) {
    http.HandleFunc(mux, "/dashboard", adminDashboardHandler)
    http.HandleFunc(mux, "/users", adminUsersHandler)
    http.HandleFunc(mux, "/settings", adminSettingsHandler)
}

Path Parameters and Query Strings

Extracting Path Parameters

Path Parameters
import http
import strings
import fmt

function setupParameterizedRoutes() {
    var mux = http.NewMux()[0]

    // Routes with path parameters
    http.HandleFunc(mux, "/users/", userHandler)           // /users/{id}
    http.HandleFunc(mux, "/posts/", postHandler)           // /posts/{id}
    http.HandleFunc(mux, "/categories/", categoryHandler)  // /categories/{slug}

    return mux
}

function userHandler(request map, response map) {
    var path = request["path"]
    var userId = extractPathParameter(path, "/users/")

    if userId == "" {
        http.WriteResponse(response, 400, "application/json", "{\"error\": \"User ID required\"}")
        return
    }

    var user = {"id": userId, "name": "User " + userId}
    var jsonData, _ = json.Marshal(user)
    http.WriteResponse(response, 200, "application/json", jsonData)
}

function postHandler(request map, response map) {
    var path = request["path"]
    var postId = extractPathParameter(path, "/posts/")
    var query = request["query"]

    // Handle query parameters
    var includeComments = query["comments"] == "true"
    var format = query["format"]

    var post = {
        "id": postId,
        "title": "Post " + postId,
        "content": "This is post content...",
        "includeComments": includeComments
    }

    if format == "xml" {
        var xmlData = convertToXML(post)  // Simplified
        http.WriteResponse(response, 200, "application/xml", xmlData)
    } else {
        var jsonData, _ = json.Marshal(post)
        http.WriteResponse(response, 200, "application/json", jsonData)
    }
}

function extractPathParameter(path string, prefix string) {
    if strings.HasPrefix(path, prefix) {
        var param = strings.TrimPrefix(path, prefix)
        var parts = strings.Split(param, "/")
        if len(parts) > 0 and parts[0] != "" {
            return parts[0]
        }
    }
    return ""
}

Query String Processing

Query String Processing
import http
import strings
import fmt

function searchHandler(request map, response map) {
    var query = request["query"]

    // Extract search parameters
    var searchTerm = query["q"]
    var category = query["category"]
    var page = query["page"]
    var limit = query["limit"]
    var sortBy = query["sort"]
    var order = query["order"]

    // Set defaults
    if page == None { page = "1" }
    if limit == None { limit = "10" }
    if sortBy == None { sortBy = "created_at" }
    if order == None { order = "desc" }

    // Validate parameters
    if searchTerm == None or searchTerm == "" {
        var errorResponse = {"error": "Search term 'q' is required"}
        var jsonError, _ = json.Marshal(errorResponse)
        http.WriteResponse(response, 400, "application/json", jsonError)
        return
    }

    // Simulate search results
    var results = [
        {"id": 1, "title": "Result 1", "category": category},
        {"id": 2, "title": "Result 2", "category": category}
    ]

    var searchResponse = {
        "query": searchTerm,
        "category": category,
        "page": page,
        "limit": limit,
        "sort": sortBy,
        "order": order,
        "results": results,
        "total": len(results)
    }

    var jsonData, _ = json.Marshal(searchResponse)
    http.WriteResponse(response, 200, "application/json", jsonData)
}

Error Handling in Routes

404 Not Found Handler

404 Handler
import http
import json

function setup404Handler() {
    var mux = http.NewMux()[0]

    // Register specific routes
    http.HandleFunc(mux, "/", homeHandler)
    http.HandleFunc(mux, "/api/users", usersHandler)

    // Catch-all for 404s (this would be handled by middleware)
    http.HandleFunc(mux, "/", notFoundHandler)

    return mux
}

function notFoundHandler(request map, response map) {
    var path = request["path"]
    var method = request["method"]

    var errorResponse = {
        "error": "Not Found",
        "message": "The requested resource was not found",
        "path": path,
        "method": method,
        "status": 404
    }

    var jsonData, _ = json.Marshal(errorResponse)
    http.WriteResponse(response, 404, "application/json", jsonData)
}

Route-Specific Error Handling

Route Error Handling
import http
import json
import fmt

function safeRouteHandler(request map, response map) {
    // Wrap handler logic in error handling
    var result = processRequest(request)
    var data = result[0]
    var err = result[1]

    if err != None {
        handleRouteError(err, response)
        return
    }

    var jsonData, jsonErr = json.Marshal(data)
    if jsonErr != None {
        handleRouteError("JSON encoding failed: " + jsonErr, response)
        return
    }

    http.WriteResponse(response, 200, "application/json", jsonData)
}

function handleRouteError(error string, response map) {
    fmt.Printf("Route error: %s\n", error)

    var errorResponse = {
        "error": "Internal Server Error",
        "message": "An error occurred processing your request",
        "timestamp": "2024-01-01T12:00:00Z"
    }

    var jsonData, _ = json.Marshal(errorResponse)
    http.WriteResponse(response, 500, "application/json", jsonData)
}

function processRequest(request map) {
    // Simulate request processing that might fail
    var success = true  // This would be actual processing logic

    if success {
        var data = {"message": "Request processed successfully"}
        return data, None
    } else {
        return None, "Processing failed"
    }
}

Best Practices

1. Use Clear URL Patterns

Clear URL Patterns
1
2
3
4
5
6
7
8
// Good - Clear and RESTful
http.HandleFunc(mux, "/api/users", usersHandler)
http.HandleFunc(mux, "/api/users/", userHandler)
http.HandleFunc(mux, "/api/posts", postsHandler)

// Bad - Unclear patterns
http.HandleFunc(mux, "/u", usersHandler)
http.HandleFunc(mux, "/data", dataHandler)
Group Related Routes
// API routes
http.HandleFunc(mux, "/api/users", usersAPIHandler)
http.HandleFunc(mux, "/api/posts", postsAPIHandler)

// Admin routes  
http.HandleFunc(mux, "/admin/users", adminUsersHandler)
http.HandleFunc(mux, "/admin/settings", adminSettingsHandler)

// Public routes
http.HandleFunc(mux, "/", homeHandler)
http.HandleFunc(mux, "/about", aboutHandler)

3. Handle Different HTTP Methods

Handle HTTP Methods
function resourceHandler(request map, response map) {
    var method = request["method"]

    match method {
        "GET" => handleGet(request, response),
        "POST" => handlePost(request, response),
        "PUT" => handlePut(request, response),
        "DELETE" => handleDelete(request, response),
        _ => methodNotAllowed(response)
    }
}

4. Validate Path Parameters

Validate Path Parameters
function userHandler(request map, response map) {
    var userId = extractPathParameter(request["path"], "/users/")

    if userId == "" {
        http.WriteResponse(response, 400, "application/json", "{\"error\": \"User ID required\"}")
        return
    }

    // Validate user ID format (e.g., numeric)
    if !isValidUserId(userId) {
        http.WriteResponse(response, 400, "application/json", "{\"error\": \"Invalid user ID format\"}")
        return
    }

    // Process valid request
    processUserRequest(userId, request, response)
}

5. Use Consistent Response Formats

Consistent Response Formats
// Success response format
{
    "data": {...},
    "status": "success",
    "timestamp": "2024-01-01T12:00:00Z"
}

// Error response format
{
    "error": "Error message",
    "code": "ERROR_CODE",
    "status": "error",
    "timestamp": "2024-01-01T12:00:00Z"
}

Next Steps