Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

fiberresp 🚨

Go Report Card Go Reference License: MIT Go Version

Elegant HTTP response handling for Fiber applications with support for custom errors and internationalization.

✨ Features

  • 🎯 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

📊 Performance

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/op

The previous github.com/prongbang/fibererror module should remain available as the legacy v1 module for existing users.

📦 Installation

go get github.com/prongbang/fiberresp

🚀 Quick Start

Use fiberresp.Response for the shortest and fastest no-config path:

Basic Usage with Standard HTTP Status

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")
}

Custom Response Body

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.

🛠️ Advanced Usage

Custom Error Types

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")
}

🌍 Internationalization Support

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.

1. Create localization files

localize/en.yaml:

custom.error: Custom error {{.ID}}

localize/th.yaml:

custom.error: ข้อผิดพลาดแบบกำหนดเอง {{.ID}}

2. Configure i18n with fiberresp

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)
    }
}

📝 Configuration Options

fiberresp.Config

Option Type Description
Custom *Custom Custom error response handler
I18n *I18n Internationalization configuration

Recommended API

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

Body Constructors

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")

Body Methods

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

fiberresp.I18n

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 Key and 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:

  1. MessageKey() string
  2. MessageKey string field
  3. goerror.Body.Message

Arguments are optional and resolved in this order:

  1. MessageArgs() any
  2. MessageArgs, Arguments, or Args field

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:

  1. StatusCode() int so fiberresp can write the response directly without a custom handler switch
  2. SetMessage(message string) so i18n can write the localized message without reflection

🔍 Examples

Returning From Application Layers

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.

Response Format

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"
}

🤝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

📄 License

This project is licensed under the MIT License - see the LICENSE file for details.

💖 Support the Project

If you find this package helpful, please consider supporting it:

"Buy Me A Coffee"

🔗 Related Projects

  • goerror - Error handling utilities for Go
  • Fiber - Express-inspired web framework
  • fiberi18n - i18n middleware for Fiber

About

Create a custom response using the error interface in Go

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages