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.
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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
BirdNest Tech Talk
Author of the rpcx microservice framework, original book author, and chair of Baidu's Go CMC committee.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
