Skip to main content
The proxy layer runs before route matching, allowing you to intercept, redirect, rewrite, or block requests globally.

Overview

type ProxyFunc func(c *Context) (*ProxyResult, error)
The proxy function receives a context and returns a ProxyResult that determines what happens to the request:
  • Continue - Proceed with normal routing
  • Redirect - Send client to a different URL
  • Rewrite - Internally change the request path
  • Response - Return a response immediately (skip routing)
The proxy runs before route matching, so you cannot access route parameters (c.Param()) in the proxy. Use path parsing or middleware for route-specific logic.

Proxy Actions

Continue

Continue with normal routing.
func Proxy(c *fuego.Context) (*fuego.ProxyResult, error) {
    // Let the request proceed to the router
    return fuego.Continue(), nil
}
This is the default action when the proxy doesn’t need to intervene.
Redirect the client to a different URL.
fuego.Redirect(url string, statusCode int) *ProxyResult
Parameters:
NameTypeDescription
urlstringTarget URL (absolute or relative)
statusCodeintHTTP status code (301, 302, 307, 308)
Status codes:
CodeNameUse Case
301Moved PermanentlyPermanent URL change, cacheable
302FoundTemporary redirect (common default)
307Temporary RedirectPreserves HTTP method
308Permanent RedirectPermanent, preserves HTTP method
Examples:
// Redirect HTTP to HTTPS
func Proxy(c *fuego.Context) (*fuego.ProxyResult, error) {
    if c.Request().TLS == nil {
        url := "https://" + c.Request().Host + c.Path()
        return fuego.Redirect(url, 301), nil
    }
    return fuego.Continue(), nil
}
// Redirect www to non-www
func Proxy(c *fuego.Context) (*fuego.ProxyResult, error) {
    host := c.Request().Host
    if strings.HasPrefix(host, "www.") {
        newHost := strings.TrimPrefix(host, "www.")
        url := "https://" + newHost + c.Path()
        return fuego.Redirect(url, 301), nil
    }
    return fuego.Continue(), nil
}
Internally rewrite the request path. The client doesn’t see the change.
fuego.Rewrite(path string) *ProxyResult
Parameters:
NameTypeDescription
pathstringNew internal path
Examples:
// Rewrite /v1/* to /api/*
func Proxy(c *fuego.Context) (*fuego.ProxyResult, error) {
    path := c.Path()
    if strings.HasPrefix(path, "/v1/") {
        newPath := "/api/" + strings.TrimPrefix(path, "/v1/")
        return fuego.Rewrite(newPath), nil
    }
    return fuego.Continue(), nil
}
// A/B testing - route 10% of users to new UI
func Proxy(c *fuego.Context) (*fuego.ProxyResult, error) {
    if c.Path() == "/" && rand.Float32() < 0.1 {
        return fuego.Rewrite("/new-homepage"), nil
    }
    return fuego.Continue(), nil
}
Use rewrites for versioning, A/B testing, or gradually migrating routes without changing client URLs.
Return a response immediately, skipping the router entirely.

Response(statusCode, body, contentType)

fuego.Response(statusCode int, body []byte, contentType string) *ProxyResult
Parameters:
NameTypeDescription
statusCodeintHTTP status code
body[]byteResponse body
contentTypestringContent-Type header
return fuego.Response(503, []byte("Service Unavailable"), "text/plain"), nil

ResponseJSON(statusCode, json)

fuego.ResponseJSON(statusCode int, json string) *ProxyResult
Return a JSON response.
return fuego.ResponseJSON(401, `{"error":"unauthorized","message":"Invalid API key"}`), nil

ResponseHTML(statusCode, html)

fuego.ResponseHTML(statusCode int, html string) *ProxyResult
Return an HTML response.
return fuego.ResponseHTML(503, `
    <!DOCTYPE html>
    <html>
    <head><title>Maintenance</title></head>
    <body>
        <h1>Under Maintenance</h1>
        <p>We'll be back shortly.</p>
    </body>
    </html>
`), nil

Response Modifiers

Chain methods to add headers to proxy responses:

WithHeader(key, value)

return fuego.ResponseJSON(200, `{"status":"ok"}`).
    WithHeader("X-Custom", "value"), nil

WithHeaders(headers)

return fuego.Redirect("/login", 302).
    WithHeaders(map[string]string{
        "X-Redirect-Reason": "session-expired",
        "Cache-Control":     "no-store",
    }), nil

ProxyConfig

Configure which requests the proxy handles:
type ProxyConfig struct {
    Matchers []string  // Path patterns to match
    Excludes []string  // Paths to exclude
}

Setting Proxy with Config

app.SetProxy(proxyFunc, &fuego.ProxyConfig{
    Matchers: []string{"/api/*", "/admin/*"},
    Excludes: []string{"/api/health", "/api/docs/*"},
})

Pattern Syntax

PatternMatches
/api/*/api/users, /api/posts/123
/users/{id}/users/123, /users/abc
/docs/**/docs/, /docs/api/context
*All paths
If no config is provided, the proxy runs on all requests.

Common Patterns

Authentication Check

func Proxy(c *fuego.Context) (*fuego.ProxyResult, error) {
    path := c.Path()
    
    // Skip auth for public paths
    publicPaths := []string{"/", "/api/health", "/api/auth/login", "/static/"}
    for _, p := range publicPaths {
        if strings.HasPrefix(path, p) {
            return fuego.Continue(), nil
        }
    }
    
    // Check API key
    apiKey := c.Header("X-API-Key")
    if apiKey == "" {
        return fuego.ResponseJSON(401, `{"error":"unauthorized","message":"API key required"}`), nil
    }
    
    // Validate API key
    if !isValidAPIKey(apiKey) {
        return fuego.ResponseJSON(401, `{"error":"unauthorized","message":"Invalid API key"}`), nil
    }
    
    return fuego.Continue(), nil
}

Rate Limiting

var (
    requestCounts = make(map[string]int)
    mu            sync.Mutex
)

func Proxy(c *fuego.Context) (*fuego.ProxyResult, error) {
    ip := c.ClientIP()
    
    mu.Lock()
    count := requestCounts[ip]
    requestCounts[ip] = count + 1
    mu.Unlock()
    
    if count > 100 { // 100 requests per window
        return fuego.ResponseJSON(429, `{"error":"rate_limit_exceeded"}`).
            WithHeader("Retry-After", "60"), nil
    }
    
    return fuego.Continue(), nil
}

Maintenance Mode

var maintenanceMode = false
var allowedIPs = []string{"192.168.1.100", "10.0.0.1"}

func Proxy(c *fuego.Context) (*fuego.ProxyResult, error) {
    if !maintenanceMode {
        return fuego.Continue(), nil
    }
    
    // Allow certain IPs during maintenance
    clientIP := c.ClientIP()
    for _, ip := range allowedIPs {
        if clientIP == ip {
            return fuego.Continue(), nil
        }
    }
    
    return fuego.ResponseHTML(503, `
        <!DOCTYPE html>
        <html>
        <head><title>Maintenance</title></head>
        <body style="font-family: sans-serif; text-align: center; padding: 50px;">
            <h1>🔧 Under Maintenance</h1>
            <p>We're performing scheduled maintenance. Please check back soon.</p>
        </body>
        </html>
    `), nil
}

Geolocation Routing

func Proxy(c *fuego.Context) (*fuego.ProxyResult, error) {
    country := c.Header("CF-IPCountry") // Cloudflare header
    
    switch country {
    case "DE", "FR", "IT", "ES":
        return fuego.Rewrite("/eu" + c.Path()), nil
    case "CN", "JP", "KR":
        return fuego.Rewrite("/asia" + c.Path()), nil
    default:
        return fuego.Continue(), nil
    }
}

Legacy URL Support

func Proxy(c *fuego.Context) (*fuego.ProxyResult, error) {
    path := c.Path()
    
    // Map old URLs to new ones
    redirects := map[string]string{
        "/old-page":     "/new-page",
        "/blog":         "/posts",
        "/about-us":     "/about",
        "/contact-form": "/contact",
    }
    
    if newPath, ok := redirects[path]; ok {
        return fuego.Redirect(newPath, 301), nil
    }
    
    return fuego.Continue(), nil
}

File-based Proxy

When using file-based routing, create app/proxy.go:
// app/proxy.go
package app

import "github.com/abdul-hamid-achik/fuego/pkg/fuego"

func Proxy(c *fuego.Context) (*fuego.ProxyResult, error) {
    // Your proxy logic here
    return fuego.Continue(), nil
}
The proxy is automatically discovered and registered.
Use the CLI to generate a proxy with common patterns:
fuego generate proxy --template auth-check
fuego generate proxy --template rate-limit
fuego generate proxy --template maintenance

Proxy vs Middleware

FeatureProxyMiddleware
RunsBefore routingAfter routing
Access to route paramsNoYes
Can skip routing entirelyYesNo
Can modify request pathYes (rewrite)No
ScopeGlobalGlobal or per-route
Use caseAuth, redirects, rewritesLogging, headers, auth
Use Proxy for:
  • Global authentication before routes are matched
  • URL redirects and rewrites
  • Blocking requests (maintenance mode)
  • A/B testing routing
Use Middleware for:
  • Request/response logging
  • Adding headers
  • Route-specific authentication
  • Request validation

Next Steps