HTTP Error Handling in Go: Chi, Gin, and Echo
Standardize HTTP error responses in Go with err-envelope. Works with net/http, Chi, Gin, and Echo. Get machine-readable error codes, field validation, trace IDs, and retry signals for better API error handling.
- tags
- #Go #Golang #Api-Design #Error-Handling #Http #Rest-Api #Chi #Gin #Echo #Middleware
- categories
- Go-Libraries Tutorials
- published
- reading time
- 5 minutes
Your Go API returns errors in three different formats. Your mobile app needs three parsing strategies to handle them all.
Here’s how to standardize HTTP error handling across your entire Go API–whether you’re using net/http, Chi router, Gin framework, or Echo framework.
The Problem
| |
Three different formats. Three parsing strategies. No trace IDs to find requests in logs.
What Good Looks Like
Every error should have the same shape:
| |
Five fields, each with a job:
code: Machine-readable identifier (never changes)message: Human-readable explanation (may evolve)details: Structured context (field errors, metadata)trace_id: Request correlation for debuggingretryable: Signal for automatic retry logic
Mobile apps can parse this once and handle every error intelligently. Field validation highlights specific inputs. Trace IDs go in bug reports. Retryable errors get automatic retry logic.
The Solution
I built err-envelope to standardize this. It’s ~300 lines of stdlib-only Go code that gives you:
| |
Every call produces the same structured response. One HTTP header set (X-Request-Id), one JSON format, one parsing strategy on the client.
Getting Started with err-envelope
Installation is a single go get command:
| |
Works immediately with:
- stdlib net/http - Use
errenvelope.Write()anderrenvelope.TraceMiddleware()directly - Chi router - Import
github.com/blackwell-systems/err-envelope/integrations/chi - Gin framework - Import
github.com/blackwell-systems/err-envelope/integrations/gin - Echo framework - Import
github.com/blackwell-systems/err-envelope/integrations/echo
Zero dependencies beyond the frameworks themselves. The core package is ~300 lines of stdlib-only Go code.
Why This Matters for Mobile Apps
Before err-envelope:
| |
Hardcoded strings. No field-level details. No trace IDs. No retry hints.
After err-envelope:
| |
Field-specific validation. Trace IDs in bug reports. Automatic retry on transient failures.
The Three Things That Make This Work
1. Stable Error Codes
Error codes never change. VALIDATION_FAILED will always be VALIDATION_FAILED. Client code can depend on these without version coupling.
Messages can evolve (“Invalid input” → “Invalid input data”) without breaking clients because code-based logic doesn’t parse strings.
2. Trace Middleware
| |
Generates or propagates trace IDs. Adds them to errors automatically. Sets X-Request-Id header for log correlation.
When a user reports “signup failed,” you search logs by trace ID and find the exact request context within seconds.
3. Arbitrary Error Mapping
| |
Maps context.DeadlineExceeded → Timeout, context.Canceled → Canceled, unknown errors → Internal. You don’t have to check error types manually.
Framework Integration: Chi, Gin, and Echo
err-envelope works with stdlib net/http by default, but also provides thin adapters for popular Go web frameworks.
Chi Router
Chi is net/http-native, so you can use errenvelope.TraceMiddleware directly. The adapter exists for convenience:
| |
Gin Framework
Gin requires a different signature, so the adapter provides errgin.Write() that extracts http.ResponseWriter and *http.Request from gin.Context:
| |
Echo Framework
Echo uses echo.Context and expects handlers to return errors. The adapter provides errecho.Write() that returns an error for Echo’s error handling chain:
| |
All three frameworks get the same structured error responses with trace IDs, field validation, and retry signals.
When Not to Use This
If you’re already standardized on RFC 9457 Problem Details, don’t switch. The two formats can coexist (map between them at API boundaries if needed).
If your API returns errors in ten different ways, this helps. If your errors are already consistent, you don’t need another abstraction.
The Result
I integrated err-envelope into Pipeboard’s mobile backend. Replaced 21 http.Error() calls with structured responses. Updated the Android app to parse the new format with field-level validation and trace IDs.
Total time: two hours. The mobile app can now highlight which form fields are invalid and include trace IDs in bug reports.
For a library that’s ~300 lines, the impact is disproportionate. That’s the sign of a good abstraction–small enough to trust, boring enough to adopt, useful enough to keep.
Code: github.com/blackwell-systems/err-envelope Docs: pkg.go.dev License: MIT