All posts

Go Error Handling Patterns for Production Code

Learn idiomatic Go error handling patterns including error wrapping, custom types, sentinel errors, and structured error reporting.

Go Error Handling Patterns for Production Code

Go's explicit error handling is a strength when done right. Here are patterns that scale.

Wrap Errors with Context

Always add context when propagating errors:

func GetUser(id string) (*User, error) {
    user, err := db.QueryUser(id)
    if err != nil {
        return nil, fmt.Errorf("get user %s: %w", id, err)
    }
    return user, nil
}

The %w verb wraps the original error, preserving the chain for errors.Is() and errors.As() checks.

Define Custom Error Types

type NotFoundError struct {
    Resource string
    ID       string
}

func (e *NotFoundError) Error() string {
    return fmt.Sprintf("%s %s not found", e.Resource, e.ID)
}

// Usage
func GetOrder(id string) (*Order, error) {
    order, err := repo.Find(id)
    if err != nil {
        return nil, &NotFoundError{Resource: "order", ID: id}
    }
    return order, nil
}

// Caller checks error type
var notFound *NotFoundError
if errors.As(err, &notFound) {
    http.Error(w, notFound.Error(), http.StatusNotFound)
    return
}

Sentinel Errors for Known Conditions

var (
    ErrNotFound     = errors.New("not found")
    ErrUnauthorized = errors.New("unauthorized")
    ErrConflict     = errors.New("conflict")
)

// Check with errors.Is
if errors.Is(err, ErrNotFound) {
    w.WriteHeader(http.StatusNotFound)
}

Panic Recovery in HTTP Handlers

func RecoveryMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        defer func() {
            if rec := recover(); rec != nil {
                log.Error().Interface("panic", rec).
                    Str("stack", string(debug.Stack())).Msg("panic recovered")
                http.Error(w, "Internal server error", 500)
            }
        }()
        next.ServeHTTP(w, r)
    })
}

Best Practices Summary

  • Always handle errors — never use _ for error returns
  • Wrap with context — use fmt.Errorf with %w
  • Check error types, not strings — use errors.Is and errors.As
  • Log at the top level — don't log and return; do one or the other
  • Report to Bugsly — capture panics and unexpected errors in production for immediate visibility into issues your tests didn't catch

Try Bugsly Free

AI-powered error tracking that explains your bugs. Set up in 2 minutes, free forever for small projects.

Get Started Free