Simplify Go HTTP Error Handling with a Unified Wrapper Pattern

Learn how to eliminate repetitive error checks in Go HTTP servers by returning errors from handlers, defining a custom HTTPError type, wrapping handlers with an error‑handling middleware, and using concise helper functions, resulting in cleaner, more maintainable code.

BirdNest Tech Talk
BirdNest Tech Talk
BirdNest Tech Talk
Simplify Go HTTP Error Handling with a Unified Wrapper Pattern

If you have written Go HTTP servers, you have probably grown tired of repeatedly writing the same error‑handling boilerplate in each handler.

func SomeHandler(w http.ResponseWriter, r *http.Request) {
    data, err := fetchSomeData()
    if err != nil {
        http.Error(w, "Failed to fetch data", http.StatusInternalServerError)
        log.Printf("Error fetching data: %v", err)
        return
    }
    // More if‑err blocks...
}

This code is highly repetitive and clutters the business logic with boilerplate.

Better approach

The core idea is simple: change your handlers to return an error instead of handling it directly.

Step 1: Define a custom HTTP error

package httperror

import (
    "errors"
    "net/http"
)

type HTTPError struct {
    error
    Code int
}

func New(code int, message string) *HTTPError {
    return &HTTPError{
        error: errors.New(message),
        Code:  code,
    }
}

func NotFound(message string) *HTTPError {
    return New(http.StatusNotFound, message)
}

// Add more helpers as needed...

Step 2: Create a handler wrapper

type HTTPHandlerWithErr func(http.ResponseWriter, *http.Request) error

func (r *Router) Handle(pattern string, handler HTTPHandlerWithErr) {
    r.HandleFunc(pattern, func(w http.ResponseWriter, req *http.Request) {
        if err := handler(w, req); err != nil {
            var httpErr *httperror.HTTPError
            if errors.As(err, &httpErr) {
                http.Error(w, err.Error(), httpErr.Code)
                slog.Debug("http error", "code", httpErr.Code, "err", err.Error())
            } else {
                http.Error(w, err.Error(), http.StatusInternalServerError)
                slog.Error("internal server error", "err", err.Error())
            }
        }
    })
}

This wrapper does all heavy lifting: it uses errors.As() to check whether the returned error is an HTTPError and extracts the status code, otherwise it defaults to 500.

Step 3: Add method‑specific helpers

func (r *Router) Get(pattern string, handler HTTPHandlerWithErr) {
    r.Handle("GET "+pattern, handler)
}

// Add Post, Put, Patch, Delete methods...

Step 4: Write concise handlers

func (c *ContainersController) Show(w http.ResponseWriter, r *http.Request) error {
    id := r.PathValue("id")

    container, err := c.service.FindContainer(id)
    if err != nil {
        if errors.Is(err, store.ErrNotFound) {
            return httperror.NotFound("container not found")
        }
        return err
    }

    return json.NewEncoder(w).Encode(container)
}

The resulting handler focuses solely on its core work; error handling is delegated to the wrapper.

What’s next?

Use JSON responses: return API errors in a structured JSON format.

Add request IDs: propagate a request identifier through logs and responses.

Build error‑aware middleware: create middleware that cooperates with error‑returning handlers.

Improve error pages: replace plain‑text errors with user‑friendly error pages.

This pattern works with any router that accepts standard Go handlers. It is a small change that yields a huge impact on code quality and maintainability.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

backendMiddlewareHTTPWeb developmentError Handling
BirdNest Tech Talk
Written by

BirdNest Tech Talk

Author of the rpcx microservice framework, original book author, and chair of Baidu's Go CMC committee.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.