A Go implementation of RFC 7807 — Problem Details for HTTP APIs. *Problem implements http.Handler and can be mounted on any mux or called inline.
- Pre-built constructors for all standard 4xx and 5xx status codes
- Functional composition —
New,From, and option functions; no setters, no mutation - Extensions — arbitrary RFC 7807 top-level extension fields via
With - Zero dependencies
go get lowbit.dev/problemjson// mount directly on a mux — Instance is set from r.URL.Path automatically
mux.Handle("/healthz", problem.ServiceUnavailable())
// inline in a handler
func handleGet(w http.ResponseWriter, r *http.Request) {
problem.NotFound(
problem.Detail("user 42 does not exist"),
).ServeHTTP(w, r)
}
// when you only have a ResponseWriter (tests, middleware)
problem.InternalServerError().Write(w)New builds a problem from scratch:
p := problem.New(
problem.Status(http.StatusUnprocessableEntity),
problem.Title("Validation failed"),
problem.Detail("the request body failed schema validation"),
problem.With("field", "email"),
problem.With("constraint", "must be a valid email address"),
)
p.ServeHTTP(w, r)From derives a new problem from an existing one without mutating it — safe for package-level bases:
var ErrOutOfStock = problem.NotFound(problem.Title("Product not available"))
func handleOrder(w http.ResponseWriter, r *http.Request) {
problem.From(ErrOutOfStock,
problem.Detailf("product %q is out of stock", productID),
).ServeHTTP(w, r)
}| Option | Sets |
|---|---|
Status(int) |
HTTP status code |
Title(string) |
Short human-readable summary |
Titlef(string, ...any) |
Title with fmt.Sprintf formatting |
Detail(string) |
Longer explanation |
Detailf(string, ...any) |
Detail with fmt.Sprintf formatting |
Type(string) |
URI identifying the problem type |
Typef(string, ...any) |
Type with fmt.Sprintf formatting |
Instance(string) |
URI identifying the specific occurrence |
InstanceFromRequest(*http.Request) |
Sets instance to r.URL.Path |
With(key, value) |
Arbitrary RFC 7807 extension field |
Error(error) |
Extension field "reason" set to err.Error() |
Extension fields appear at the top level of the JSON object, as required by RFC 7807:
// produces: {"type":"...","status":422,"title":"...","field":"email","reason":"invalid format"}
problem.New(
problem.Status(422),
problem.Title("Validation failed"),
problem.With("field", "email"),
problem.Error(err),
)