Skip to main content
Fuego provides structured HTTP error types and helper functions for consistent error responses.

HTTPError Struct

type HTTPError struct {
    Code    int    `json:"code"`
    Message string `json:"message"`
    Err     error  `json:"-"` // Not exposed to client
}
FieldTypeDescription
CodeintHTTP status code
MessagestringError message returned to client
ErrerrorUnderlying error (for logging, not sent to client)

Methods

func (e *HTTPError) Error() string   // Returns message
func (e *HTTPError) Unwrap() error   // Returns underlying Err

Error Helpers

Convenient functions for common HTTP errors:

BadRequest

400 Bad Request

Unauthorized

401 Unauthorized

Forbidden

403 Forbidden

NotFound

404 Not Found

Conflict

409 Conflict

InternalServerError

500 Internal Server Error

Usage

func Post(c *fuego.Context) error {
    var req CreateUserRequest
    if err := c.Bind(&req); err != nil {
        return fuego.BadRequest("invalid request body")
    }
    
    if req.Email == "" {
        return fuego.BadRequest("email is required")
    }
    
    // ...
}
Response:
{
  "error": "bad_request",
  "message": "email is required"
}

Creating Custom Errors

NewHTTPError

Create a custom HTTP error with any status code:
fuego.NewHTTPError(code int, message string) *HTTPError
// 402 Payment Required
return fuego.NewHTTPError(402, "subscription required")

// 418 I'm a teapot
return fuego.NewHTTPError(418, "cannot brew coffee")

// 429 Too Many Requests
return fuego.NewHTTPError(429, "rate limit exceeded")

// 503 Service Unavailable
return fuego.NewHTTPError(503, "service under maintenance")

NewHTTPErrorWithCause

Create an error with an underlying cause for debugging:
fuego.NewHTTPErrorWithCause(code int, message string, err error) *HTTPError
user, err := db.FindUser(id)
if err != nil {
    // err is logged but not sent to client
    return fuego.NewHTTPErrorWithCause(500, "failed to fetch user", err)
}
The underlying error can be retrieved with errors.Unwrap():
if httpErr, ok := fuego.IsHTTPError(err); ok {
    if cause := errors.Unwrap(httpErr); cause != nil {
        log.Printf("Underlying error: %v", cause)
    }
}

Wrapping Errors

WrapError

Wrap an existing error with additional context:
fuego.WrapError(err error, message string) error
func fetchUserData(id string) (*User, error) {
    user, err := db.FindUser(id)
    if err != nil {
        return nil, fuego.WrapError(err, "failed to fetch user from database")
    }
    
    profile, err := api.GetProfile(user.ProfileID)
    if err != nil {
        return nil, fuego.WrapError(err, "failed to fetch user profile")
    }
    
    return user, nil
}

Error Checking

IsHTTPError

Check if an error is an HTTPError and extract it:
fuego.IsHTTPError(err error) (*HTTPError, bool)
func errorMiddleware() fuego.MiddlewareFunc {
    return func(next fuego.HandlerFunc) fuego.HandlerFunc {
        return func(c *fuego.Context) error {
            err := next(c)
            
            if httpErr, ok := fuego.IsHTTPError(err); ok {
                // Log the error
                log.Printf("[%d] %s", httpErr.Code, httpErr.Message)
                
                // Handle specific codes
                if httpErr.Code == 401 {
                    // Clear auth cookie
                    c.SetCookie(&http.Cookie{
                        Name:   "session",
                        MaxAge: -1,
                    })
                }
            }
            
            return err
        }
    }
}

Error Response Format

All HTTP errors are returned as JSON:
{
  "error": "error_code",
  "message": "Human readable message"
}
Status CodeError Code
400bad_request
401unauthorized
403forbidden
404not_found
409conflict
500internal_server_error

Custom Error Responses

For more control over error responses, use c.JSON() directly:
type ErrorResponse struct {
    Error   string            `json:"error"`
    Message string            `json:"message"`
    Details map[string]string `json:"details,omitempty"`
    TraceID string            `json:"trace_id,omitempty"`
}

func Post(c *fuego.Context) error {
    var req CreateUserRequest
    if err := c.Bind(&req); err != nil {
        return c.JSON(400, ErrorResponse{
            Error:   "validation_error",
            Message: "Invalid request body",
            Details: map[string]string{
                "body": "must be valid JSON",
            },
            TraceID: c.Header("X-Request-ID"),
        })
    }
    
    errors := validateUser(req)
    if len(errors) > 0 {
        return c.JSON(422, ErrorResponse{
            Error:   "validation_error",
            Message: "Validation failed",
            Details: errors,
            TraceID: c.Header("X-Request-ID"),
        })
    }
    
    // ...
}

Error Handling Middleware

Create a centralized error handler:
func ErrorHandler() fuego.MiddlewareFunc {
    return func(next fuego.HandlerFunc) fuego.HandlerFunc {
        return func(c *fuego.Context) error {
            err := next(c)
            if err == nil {
                return nil
            }
            
            // Check if it's already an HTTP error
            if httpErr, ok := fuego.IsHTTPError(err); ok {
                return c.JSON(httpErr.Code, map[string]string{
                    "error":   httpCodeToString(httpErr.Code),
                    "message": httpErr.Message,
                })
            }
            
            // Log unexpected errors
            log.Printf("Unexpected error: %v", err)
            
            // Return generic error to client
            return c.JSON(500, map[string]string{
                "error":   "internal_server_error",
                "message": "An unexpected error occurred",
            })
        }
    }
}

func httpCodeToString(code int) string {
    switch code {
    case 400:
        return "bad_request"
    case 401:
        return "unauthorized"
    case 403:
        return "forbidden"
    case 404:
        return "not_found"
    case 409:
        return "conflict"
    default:
        return "internal_server_error"
    }
}

Best Practices

Do:
return fuego.BadRequest("email must be a valid email address")
return fuego.NotFound("user with ID 123 not found")
Don’t:
return fuego.BadRequest("invalid input")
return fuego.NotFound("not found")
Do:
if err != nil {
    log.Printf("Database error: %v", err)
    return fuego.InternalServerError("failed to process request")
}
Don’t:
if err != nil {
    return fuego.InternalServerError(err.Error()) // Exposes SQL errors!
}
SituationStatus Code
Invalid JSON body400 Bad Request
Missing required field400 Bad Request
Invalid field format400 Bad Request (or 422)
Missing/invalid auth401 Unauthorized
Valid auth, no permission403 Forbidden
Resource doesn’t exist404 Not Found
Resource already exists409 Conflict
Server error500 Internal Server Error
Service unavailable503 Service Unavailable
user, err := db.FindUser(id)
if err != nil {
    // Log the actual error
    log.Printf("Failed to find user %s: %v", id, err)
    
    // Return safe message to client
    if errors.Is(err, sql.ErrNoRows) {
        return fuego.NotFound("user not found")
    }
    return fuego.InternalServerError("failed to fetch user")
}

Next Steps