Elegant HTTP response handling for Fiber applications with support for custom errors and internationalization.
- 🎯 Simple Integration - Easy to integrate with existing Fiber applications
- 🔧 Custom Error Types - Create your own error types with custom codes
- 🌍 i18n Support - Built-in internationalization for error messages
- ⚡ High Performance - Optimized for speed and efficiency
- 🎨 Flexible Configuration - Customizable response formats
- 📦 Standard HTTP Errors - Pre-defined standard HTTP error responses
BenchmarkFiberResp_Response-12 4781 ns/op 11162 B/op 36 allocs/op
BenchmarkFiberResp_DirectResponse-12 4699 ns/op 11139 B/op 36 allocs/op
BenchmarkBuildInErrorResponse_Response-12 4730 ns/op 11136 B/op 36 allocs/op
BenchmarkFiberResp_FastCustomResponse-12 4739 ns/op 11080 B/op 36 allocs/op
BenchmarkFiberResp_ResponseFastCustomResponse-12 4679 ns/op 11081 B/op 36 allocs/op
BenchmarkFiberResp_BadRequestBody-12 4646 ns/op 11063 B/op 36 allocs/op
BenchmarkFiberResp_ErrorBody-12 5213 ns/op 11038 B/op 36 allocs/op
BenchmarkFiberResp_BadRequestBodyWithParam-12 4894 ns/op 11401 B/op 38 allocs/opThe previous
github.com/prongbang/fibererrormodule should remain available as the legacy v1 module for existing users.
go get github.com/prongbang/fiberrespUse fiberresp.Response for the shortest and fastest no-config path:
package main
import (
"github.com/prongbang/goerror"
"github.com/prongbang/fiberresp"
"github.com/gofiber/fiber/v2"
)
func main() {
app := fiber.New()
app.Get("/", func(c *fiber.Ctx) error {
return fiberresp.Response(c, goerror.NewUnauthorized())
})
_ = app.Listen(":3000")
}func handler(c *fiber.Ctx) error {
return fiberresp.Response(c,
fiberresp.BadRequest("VAL001", "validation.field.required").
WithParam("field", "email").
WithData(map[string]any{"field": "email"}).
WithCause("email is empty"),
)
}The message argument can be plain text when you do not use i18n:
func handler(c *fiber.Ctx) error {
return fiberresp.Response(c,
fiberresp.BadRequest("VAL001", "Email is required"),
)
}Use fiberresp.Error for any HTTP status:
func handler(c *fiber.Ctx) error {
return fiberresp.Response(c,
fiberresp.Error(http.StatusConflict, "CNF001", "conflict.resource_exists"),
)
}Convenience constructors are also available for every net/http status, for
example BadRequest, Unauthorized, NotFound, InternalServerError, and
ServiceUnavailable.
Use fiberresp.New(...).Response when you need shared configuration such as
i18n or a custom response handler.
Create your own error types with custom codes:
package main
import (
"github.com/prongbang/goerror"
"github.com/prongbang/fiberresp"
"github.com/gofiber/fiber/v2"
"net/http"
)
type CustomError struct {
goerror.Body
}
func (c *CustomError) Error() string {
return c.Message
}
func (c *CustomError) StatusCode() int {
return http.StatusBadRequest
}
func NewCustomError() error {
return &CustomError{
Body: goerror.Body{
Code: "CUS001",
Message: "Custom error occurred",
},
}
}
func main() {
app := fiber.New()
app.Get("/", func(c *fiber.Ctx) error {
return fiberresp.Response(c, NewCustomError())
})
_ = app.Listen(":3000")
}Localize error messages based on Accept-Language header. The error code
stays as an application error code, while the message key is read from
Body.Message or a custom MessageKey() string method. If i18n is disabled or
localization fails, fiberresp keeps the original message value and sends it as
plain text.
localize/en.yaml:
custom.error: Custom error {{.ID}}localize/th.yaml:
custom.error: ข้อผิดพลาดแบบกำหนดเอง {{.ID}}package main
import (
"fmt"
"net/http"
"github.com/gofiber/contrib/fiberi18n/v2"
"github.com/gofiber/fiber/v2"
"github.com/nicksnyder/go-i18n/v2/i18n"
"github.com/prongbang/fiberresp"
"github.com/prongbang/goerror"
"golang.org/x/text/language"
)
type CustomError struct {
goerror.Body
Args any `json:"-"`
}
func (c *CustomError) Error() string {
return c.Message
}
func (c *CustomError) MessageArgs() any {
return c.Args
}
func (c *CustomError) StatusCode() int {
return http.StatusBadRequest
}
func (c *CustomError) SetMessage(message string) {
c.Message = message
}
func NewCustomError() error {
return &CustomError{
Body: goerror.Body{
Code: "CUS001",
Message: "custom.error",
},
Args: map[string]any{
"ID": "001",
},
}
}
func main() {
app := fiber.New()
// Configure i18n middleware
app.Use(fiberi18n.New(&fiberi18n.Config{
RootPath: "./localize",
AcceptLanguages: []language.Tag{language.Thai, language.English},
DefaultLanguage: language.English,
}))
// Configure fiberresp with i18n
response := fiberresp.New(&fiberresp.Config{
I18n: &fiberresp.I18n{
Enabled: true,
LocalizeWithArgs: func(ctx *fiber.Ctx, messageKey string, args any) (string, error) {
return fiberi18n.Localize(ctx, &i18n.LocalizeConfig{
MessageID: messageKey,
TemplateData: args,
})
},
},
})
app.Get("/", func(c *fiber.Ctx) error {
return response.Response(c, NewCustomError())
})
err := app.Listen(":3000")
if err != nil {
fmt.Println("Error starting server:", err)
}
}| Option | Type | Description |
|---|---|---|
Custom |
*Custom |
Custom error response handler |
I18n |
*I18n |
Internationalization configuration |
| API | Use case |
|---|---|
fiberresp.Response(c, err) |
Fastest and shortest no-config response |
fiberresp.Error(status, code, key) |
Custom response body for any HTTP status |
fiberresp.Status(status, code, key) |
Alias for custom response body by HTTP status |
fiberresp.NewBody(status, code, key) |
Low-level body constructor |
fiberresp.BadRequest(code, key).WithParam(key, value) |
Short custom response body with i18n args |
fiberresp.New(config).Response(c, err) |
Reusable configured response for i18n/custom handlers |
fiberresp.New(config).With(c).Response(err) |
Backward-compatible convenience style |
fiberresp.Error, fiberresp.Status, and fiberresp.NewBody work with any
HTTP status code:
fiberresp.Error(http.StatusConflict, "CNF001", "conflict.resource_exists")
fiberresp.Status(http.StatusInternalServerError, "SRV001", "server.error")
fiberresp.NewBody(http.StatusAccepted, "JOB001", "job.accepted")Convenience constructors are available for every net/http status, including:
fiberresp.OK("OK001", "ok")
fiberresp.Created("CRT001", "created")
fiberresp.BadRequest("VAL001", "validation.failed")
fiberresp.Unauthorized("AUT001", "auth.unauthorized")
fiberresp.Forbidden("AUT002", "auth.forbidden")
fiberresp.NotFound("USR001", "user.not_found")
fiberresp.Conflict("CNF001", "conflict")
fiberresp.UnprocessableEntity("VAL002", "validation.unprocessable")
fiberresp.TooManyRequests("RAT001", "rate_limited")
fiberresp.InternalServerError("SRV001", "server.error")
fiberresp.ServiceUnavailable("SRV002", "service.unavailable")| Method | Description |
|---|---|
WithData(data any) *Body |
Set response data |
WithCause(cause string) *Body |
Set error cause |
WithParam(key string, value any) *Body |
Set one i18n template argument |
WithParams(args map[string]any) *Body |
Set many i18n template arguments |
| Option | Type | Description |
|---|---|---|
Enabled |
bool |
Enable/disable i18n support |
Localize |
func(*fiber.Ctx, string) (string, error) |
Localization function for a message key |
LocalizeWithArgs |
func(*fiber.Ctx, string, any) (string, error) |
Localization function for a message key with template arguments |
Message can be either a plain text response message or an i18n key:
fiberresp.BadRequest("VAL001", "Email is required")
fiberresp.BadRequest("VAL001", "validation.email.required")When i18n is enabled and localization succeeds, the localized text replaces
Message before the JSON response is written. Otherwise, the original
Message is returned unchanged.
fiberresp resolves i18n keys in this order:
MessageKey() stringMessageKey stringfieldgoerror.Body.Message
Arguments are optional and resolved in this order:
MessageArgs() anyMessageArgs,Arguments, orArgsfield
The localized text is written back to goerror.Body.Message before your custom
response handler serializes the error.
For the fastest custom error path, implement:
StatusCode() intso fiberresp can write the response directly without a custom handler switchSetMessage(message string)so i18n can write the localized message without reflection
Return error from datasource, repository, usecase, and handler layers. The
handler should be the single place that writes the Fiber response:
func (r *UserRepository) FindByEmail(email string) (*User, error) {
user, err := r.db.FindByEmail(email)
if errors.Is(err, sql.ErrNoRows) {
return nil, fiberresp.NotFound("USR001", "user.not_found").
WithParam("email", email)
}
if err != nil {
return nil, fiberresp.InternalServerError("USR999", "user.query_failed")
}
return user, nil
}
func (s *UserService) GetUser(email string) (*User, error) {
user, err := s.repo.FindByEmail(email)
if err != nil {
return nil, err
}
if !user.Active {
return nil, fiberresp.Forbidden("USR002", "user.inactive")
}
return user, nil
}
func (h *UserHandler) Get(c *fiber.Ctx) error {
user, err := h.service.GetUser(c.Params("email"))
if err != nil {
return fiberresp.Response(c, err)
}
return fiberresp.Response(c, goerror.NewOK(user))
}WithCause is sent to clients, so only use it for safe, public details. Avoid
putting raw SQL, stack traces, secrets, tokens, or internal hostnames in
cause.
Standard error response structure:
{
"code": "CUS001",
"message": "Custom error message"
}With additional fields:
{
"code": "VAL001",
"message": "Validation failed",
"data": {
"field": "email",
"reason": "invalid format"
},
"cause": "email is empty"
}Contributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under the MIT License - see the LICENSE file for details.
If you find this package helpful, please consider supporting it:
- goerror - Error handling utilities for Go
- Fiber - Express-inspired web framework
- fiberi18n - i18n middleware for Fiber
