How to Implement Custom Error Handlers with Error Types in Gorig

Gorig provides a structured error-handling framework based on a typed Error struct in utils/errors that maps business logic failures to HTTP responses via the apix package, enabling consistent JSON formatting, automatic logging, and panic recovery.

The jom-io/gorig repository offers a comprehensive solution to implement custom error handlers with error types that standardize how Go applications communicate failures to API consumers. By leveraging the Error struct defined in utils/errors/err.go alongside response utilities in apix/handle.go, developers can ensure that system crashes, validation failures, and coding errors each trigger appropriate HTTP status codes and uniform JSON payloads.

Understanding Gorig's Typed Error Architecture

Gorig categorizes every error into one of three distinct types: Coding, System, and Application. This classification determines how the framework logs the incident and which HTTP status code it returns to the client.

The central Error struct in utils/errors/err.go carries a Type field (string), a Code field (string for numeric identifiers), and a Message. The CodeInt() method parses the string code into an integer, allowing the API layer to override default HTTP statuses when specific business error codes are required.

Core Source Files and Responsibilities

  • utils/errors/err.go: Defines the Error struct, type constants, and constructors (Sys, Verify, Assert, VerifyCode). This is where you create typed error instances.
  • apix/handle.go: Contains Handle, HandleData, HandleError, and PanicNotify. These functions translate *errors.Error into HTTP responses and manage logging.
  • apix/response/response.go: Provides low-level JSON rendering helpers (Success, Fail, ErrorSystem) that standardize the response shape.
  • httpx/mid.recovery.go: Implements the Recovery() Gin middleware that intercepts panics and forwards them to apix.PanicNotify as typed errors.

Defining Custom Error Types

Create reusable error definitions using the constructor functions from utils/errors. Each constructor returns *errors.Error pre-configured with the appropriate type classification.

import "github.com/jom-io/gorig/utils/errors"

// Application-level error for business rule violations (maps to 4xx responses)
var ErrInsufficientBalance = errors.Verify("balance not enough for the requested operation")

// System-level error for infrastructure failures (maps to 500 responses)
var ErrDatabaseConnection = errors.Sys("cannot connect to database")

// Coding-level error for invalid arguments or assertions
var ErrInvalidUserID = errors.Assert("userID", "value cannot be empty")

// Custom numeric code for specific HTTP status mapping
var ErrResourceNotFound = errors.VerifyCode(404, "resource not found")

Key constructors:

  • Sys(message, nativeErr...): Creates System type errors for database, network, or file system failures.
  • Verify(message, nativeErr...): Creates Application type errors for validation or business logic violations.
  • VerifyCode(code, message, nativeErr...): Creates Application errors with an explicit numeric code that overrides the default HTTP status.
  • Assert(key, value): Creates Coding type errors for invalid arguments or programming assertions.

Configuring Global Error Recovery

Before handling specific errors in controllers, register the Recovery middleware to capture unhandled panics and convert them into typed errors automatically.

import "github.com/jom-io/gorig/httpx"

func main() {
    // Startup creates the Gin engine and registers Recovery middleware
    httpx.Startup("myService", "8080")
}

The Recovery() middleware defined in httpx/mid.recovery.go wraps every HTTP handler. When a panic occurs, it recovers, constructs a System type *errors.Error, and invokes apix.PanicNotify, which logs the stack trace and optionally sends alerts before returning a sanitized 500 response to the client.

Implementing Error Handlers in Controllers

The apix package provides three primary functions to implement custom error handlers in your Gin controllers. Each accepts an *errors.Error and produces a consistent JSON response with the shape {code: <int>, msg: <string>, data: <any>}.

Handling Native Errors with HandleError

Use HandleError when you need to wrap a standard Go error and control the HTTP status code explicitly.

func GetUser(c *gin.Context) {
    user, err := svc.FindUserByID(c.Param("id"))
    if err != nil {
        // Convert native error to System type and return 500
        apix.HandleError(c, http.StatusInternalServerError, nil, errors.Sys(err.Error()))
        return
    }
    apix.HandleData(c, http.StatusOK, user, nil)
}

Routing Typed Errors with Handle

Use Handle when you already have an *errors.Error (such as the custom types defined earlier) and want the framework to determine the response format based on the error's type.

func CreateOrder(c *gin.Context) {
    if err := svc.ValidateOrder(c.Request.Body); err != nil {
        // err is *errors.Error (e.g., ErrInsufficientBalance)
        // Handle inspects err.Type: Application errors trigger response.Fail()
        apix.Handle(c, http.StatusBadRequest, err)
        return
    }
    
    orderID, err := svc.Create(c)
    if err != nil {
        apix.Handle(c, http.StatusInternalServerError, errors.Sys(err.Error()))
        return
    }
    
    apix.HandleData(c, http.StatusCreated, map[string]string{"id": orderID}, nil)
}

Returning Data with Error Checks

HandleData simplifies the success path by automatically returning the provided data when the error parameter is nil, while still handling error cases consistently.

func DeleteResource(c *gin.Context) {
    if !isAdmin(c) {
        // Application error with custom code 403
        apix.Handle(c, http.StatusForbidden, errors.VerifyCode(403, "admin privilege required"))
        return
    }
    
    if err := svc.Delete(c.Param("id")); err != nil {
        apix.Handle(c, http.StatusInternalServerError, errors.Sys(err.Error()))
        return
    }
    
    // Empty success payload with 204 status
    apix.Handle(c, http.StatusNoContent, nil)
}

Summary

  • Define reusable *errors.Error instances using constructors in utils/errors/err.go to categorize failures as System, Application, or Coding.
  • Register the Recovery middleware via httpx.Startup to capture panics and convert them to typed errors automatically.
  • Route errors through apix.Handle, HandleData, or HandleError to ensure consistent JSON formatting and appropriate HTTP status codes.
  • Map custom business codes to HTTP statuses using VerifyCode and the CodeInt() method for precise API control.

Frequently Asked Questions

What is the difference between System and Application error types in Gorig?

System errors represent infrastructure failures like database connection timeouts or file system errors, and they automatically trigger HTTP 500 responses via response.ErrorSystem. Application errors represent business logic violations such as validation failures or insufficient permissions, and they use response.Fail to return 4xx status codes with detailed messages. The distinction ensures that internal failures are logged as warnings while user-facing errors are logged as standard errors.

How do I map custom error codes to specific HTTP status codes?

Use the errors.VerifyCode(code, message) constructor to create an Application type error with a numeric code string. When passed to apix.Handle, the framework calls err.CodeInt() to parse the code and overrides the default HTTP status. For example, errors.VerifyCode(404, "not found") will return HTTP 404 even if the default for validation errors is 400.

Can I use native Go errors with Gorig's custom error handlers?

Yes, but you must wrap native errors using errors.Sys(err.Error()) or errors.Verify(err.Error()) before passing them to apix.Handle or HandleError. The HandleError function accepts a native error wrapped in the *errors.Error struct, allowing you to maintain Gorig's consistent JSON response format while handling legacy error sources.

How does panic recovery integrate with custom error types?

The Recovery middleware in httpx/mid.recovery.go catches all panics during request handling and converts them into System type *errors.Error instances. It then calls apix.PanicNotify, which logs the error with stack trace information and returns a generic 500 response to the client without exposing internal details. This ensures that even unhandled runtime errors follow your established error response format.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →