fiber

package module
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 9, 2026 License: MIT Imports: 13 Imported by: 0

README

fiber

Go Reference Go Report Card

A samsara-compatible HTTP server component backed by Fiber v3.

go get github.com/sunkek/samsara-components/fiber

Usage

Register with a supervisor
import (
    "github.com/sunkek/samsara-components/fiber"
    gf "github.com/gofiber/fiber/v3"
)

api := fiber.New(fiber.Config{
    Host:       "0.0.0.0",
    Port:       8080,
    PathPrefix: "/api/v1",
})
sup.Add(api, samsara.WithTier(samsara.TierCritical))
Register domain routes

Pass a RegisterFunc to Register. It receives the root gf.Router (scoped to PathPrefix) and registers routes and sub-groups on it:

srv.Register(func(r gf.Router) {
    r.Get("/users",   handleGetUsers)
    r.Post("/users",  handleCreateUser)
    r.Delete("/users/:id", handleDeleteUser)

    // Sub-groups work naturally:
    admin := r.Group("/admin", adminAuthMiddleware)
    admin.Get("/stats", handleStats)
})

Domain adapters should call srv.Register in their constructor or wiring function, before app.Run():

// In your adapter:
func (a *UserAdapter) RegisterRoutes(srv *sc.Component) {
    srv.Register(func(r gf.Router) {
        r.Get("/users",  a.handleGetUsers)
        r.Post("/users", a.handleCreateUser)
    })
}

// In main.go:
userAdapter := user.New(db)
userAdapter.RegisterRoutes(srv)

Register is also safe to call after Start — routes are applied immediately on the live app and will be re-registered on the next restart.

Add global middleware

Use Use to inject middleware that wraps all domain routes (auth, tracing, etc.). Must be called before Start to take effect on the current run.

srv.Use(authMiddleware, tracingMiddleware)

Built-in middleware stack

Applied automatically in this order:

Middleware Notes
Recover Catches panics; stack trace enabled
CORS Configured via CORSAllowOrigins/Methods/Headers
Security headers HSTS, X-Frame-Options, CORP/COEP (disable with EnableSecurityHeaders: &false)
Compress BestSpeed level
Caller-supplied Use(...) Auth, tracing, etc.
Request logger Structured JSON; disable with LoggerFormat: "-"
Domain routes Everything registered via Register(...)

Configuration

sc.Config{
    Host       string        // default: "0.0.0.0"
    Port       int           // default: 8080
    PathPrefix string        // default: "/api/v1"
    BodyLimitMB int          // default: 4

    CORSAllowOrigins []string // default: ["*"]
    CORSAllowMethods []string // default: ["GET","POST","PUT","PATCH","DELETE","OPTIONS"]
    CORSAllowHeaders []string // default: ["*"]

    ReadTimeout  time.Duration // default: Fiber default
    WriteTimeout time.Duration // default: Fiber default
    IdleTimeout  time.Duration // default: Fiber default

    ErrorHandler  gf.ErrorHandler // default: DefaultErrorHandler
    LoggerFormat  string          // default: structured JSON; "-" disables
    EnableSecurityHeaders *bool   // default: true
}
Options
sc.WithLogger(slog.Default())   // attach a structured logger
sc.WithName("api-server")       // override component name
sc.WithSwagger(sc.SwaggerConfig{
    JSONPath: "./docs/swagger.json",
})                              // enable Swagger UI at /api/docs

Error handling

DefaultErrorHandler maps errors to HTTP status codes:

Error type Status
*gf.Error Error's own .Code
Implements HTTPStatuser .StatusCode()
Anything else 500

To integrate your own error library, implement HTTPStatuser:

type NotFoundError struct{ Resource string }
func (e *NotFoundError) Error() string   { return e.Resource + " not found" }
func (e *NotFoundError) StatusCode() int { return http.StatusNotFound }

Or supply a fully custom ErrorHandler:

cfg.ErrorHandler = func(c gf.Ctx, err error) error {
    var myErr *myapp.DomainError
    if errors.As(err, &myErr) {
        return c.Status(myErr.HTTPStatus()).JSON(sc.ErrorResponse{Error: myErr.Error()})
    }
    return sc.DefaultErrorHandler(c, err) // fallback
}

Swagger UI

sc.WithSwagger(sc.SwaggerConfig{
    JSONPath: "./docs/swagger.json",
    UIPath:   "/docs",           // default; UI available at /api/docs
})(srv)

The spec is served at /api/docs/swagger.json; the UI at /api/docs.

Generate the spec with swaggo/swag:

swag init -g main.go -o ./docs

Built-in endpoints

Endpoint Status Purpose
GET {PathPrefix}/health 204 Kubernetes/Docker health probe

The /health endpoint is registered before the logger middleware, so probe traffic does not appear in access logs.


Helper utilities

// Real client IP, honouring X-Forwarded-For:
ip := sc.RealIP(c)

// Build a middleware skipper that excludes specific routes:
skipper := sc.ExcludeRoutes(
    sc.Route{Method: "GET", Path: "/api/health"},
    sc.Route{Method: "GET", Path: "/api/metrics"},
)
srv.Use(func(c gf.Ctx) error {
    if skipper(c) { return c.Next() }
    return authMiddleware(c)
})

Health checking

*Component implements samsara.HealthChecker. The supervisor polls Health(ctx) every health interval. Health fails if:

  • The server is not listening (not yet started, or shutting down).
  • The built-in /health endpoint does not return 204.

Restart behaviour

On every Start, the full middleware stack and all registered RegisterFuncs are re-applied in registration order. This means restarts are clean — no state leaks from the previous run.

Documentation

Overview

Package fiber provides a github.com/sunkek/samsara-compatible HTTP server component backed by [Fiber v3].

Usage

srv := fiber.New(fiber.Config{
    Host: "0.0.0.0",
    Port: 8080,
})

// Register domain routes before app.Run():
srv.Register(func(r gf.Router) {
    r.Get("/users",  handleGetUsers)
    r.Post("/users", handleCreateUser)

    // Sub-groups work naturally:
    admin := r.Group("/admin", adminMiddleware)
    admin.Get("/stats", handleStats)
})

sup.Add(srv, samsara.WithTier(samsara.TierCritical))

The component calls ready() as soon as the TCP port is bound, giving the supervisor a precise "port is open" signal before any request can arrive.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func DefaultErrorHandler

func DefaultErrorHandler(c gf.Ctx, err error) error

DefaultErrorHandler maps errors to HTTP status codes and writes a JSON ErrorResponse body.

Mapping rules (evaluated in order):

  1. *gf.Error — uses the error's own Code field.
  2. HTTPStatuser — any error implementing StatusCode() int is honoured.
  3. Anything else — 500 Internal Server Error.

To integrate your own error library, supply a custom gf.ErrorHandler via [Config.ErrorHandler] and call DefaultErrorHandler as a fallback:

cfg.ErrorHandler = func(c gf.Ctx, err error) error {
    var myErr *myapp.Error
    if errors.As(err, &myErr) {
        return c.Status(myErr.HTTPStatus()).JSON(fiber.ErrorResponse{Error: myErr.Error()})
    }
    return fiber.DefaultErrorHandler(c, err)
}

func RealIP

func RealIP(c gf.Ctx) string

RealIP returns the client's real IP address. It prefers the X-Forwarded-For header (set by reverse proxies) over the direct connection IP.

Only use X-Forwarded-For if your service sits behind a trusted proxy. If clients can send arbitrary headers, use c.IP() directly instead.

Types

type Component

type Component struct {
	// contains filtered or unexported fields
}

Component is a samsara-compatible Fiber HTTP server component. Obtain one with New; register it with a samsara supervisor.

Call Component.Register to add domain routes before [samsara.Application.Run]. The component is designed to be used as follows in main.go:

srv := fiber.New(cfg)
userAdapter.RegisterRoutes(srv)  // calls srv.Register(...)
sup.Add(srv)

func New

func New(cfg Config, opts ...Option) *Component

New creates a Component from the supplied config. The HTTP server is not started until Component.Start is called.

func (*Component) Health

func (c *Component) Health(ctx context.Context) error

Health implements samsara.HealthChecker. Returns a non-nil error if the server is not currently listening.

func (*Component) Name

func (c *Component) Name() string

Name implements samsara.Component.

func (*Component) Register

func (c *Component) Register(fn RegisterFunc)

Register adds a RegisterFunc that will be called during Component.Start with the root gf.Router scoped to [Config.PathPrefix].

Register must be called before [samsara.Application.Run]. All registered funcs are applied in registration order when Start builds the Fiber app. On each restart they are re-applied automatically.

Example:

srv.Register(func(r gf.Router) {
    r.Get("/users", handleGetUsers)
    r.Post("/users", handleCreateUser)

    // Sub-groups work naturally:
    v2 := r.Group("/v2")
    v2.Get("/users", handleGetUsersV2)
})

func (*Component) Start

func (c *Component) Start(ctx context.Context, ready func()) error

Start initialises the Fiber app, applies middleware, calls all registered [RegisterFunc]s, then begins serving. Calls ready() the moment the TCP port is bound — before any request can be accepted.

Start is safe to call multiple times across restarts.

func (*Component) Stop

func (c *Component) Stop(ctx context.Context) error

Stop gracefully shuts down the HTTP server, draining in-flight requests within the context deadline.

func (*Component) Use

func (c *Component) Use(args ...any)

Use adds global middleware that is applied to all domain routes after the built-in middleware stack (recover, CORS, security headers, compress) but before domain route handlers.

Typical use: authentication, distributed tracing, rate limiting.

Must be called before Component.Start. Calling Use after Start has no effect on the current run, but the middleware will be applied on restart.

Example:

srv.Use(authMiddleware, tracingMiddleware)

type Config

type Config struct {
	// Host is the listen address. Defaults to "0.0.0.0".
	Host string
	// Port is the listen port. Defaults to 8080.
	Port int

	// PathPrefix is the URL prefix for all registered routes.
	// Defaults to "/api/v1".
	PathPrefix string

	// BodyLimitMB is the maximum request body size in megabytes. Defaults to 4.
	BodyLimitMB int

	// CORSAllowOrigins is the list of allowed CORS origins.
	// Defaults to ["*"].
	CORSAllowOrigins []string
	// CORSAllowMethods is the list of allowed CORS methods.
	// Defaults to ["GET","POST","PUT","PATCH","DELETE","OPTIONS"].
	CORSAllowMethods []string
	// CORSAllowHeaders is the list of allowed CORS headers.
	// Defaults to ["*"].
	CORSAllowHeaders []string

	// ReadTimeout is the maximum duration for reading the entire request.
	// Defaults to 5 s.
	ReadTimeout time.Duration
	// WriteTimeout is the maximum duration for writing the response.
	// Defaults to 10 s.
	WriteTimeout time.Duration
	// IdleTimeout is the maximum duration to wait for the next request.
	// Defaults to 30 s.
	IdleTimeout time.Duration

	// ErrorHandler is called when a route handler returns a non-nil error.
	// If nil, a default JSON error handler is used (see [DefaultErrorHandler]).
	ErrorHandler gf.ErrorHandler

	// LoggerFormat is the format string for the request logger middleware.
	// If empty, a structured JSON format is used. Set to "-" to disable.
	LoggerFormat string

	// EnableSecurityHeaders adds security-related response headers (HSTS,
	// X-Frame-Options, CORP/COEP). Defaults to true.
	EnableSecurityHeaders *bool
}

Config holds all configuration for the Fiber HTTP server component.

type ErrorResponse

type ErrorResponse struct {
	// Error is the human-readable error message.
	Error string `json:"error"`
}

ErrorResponse is the JSON shape returned by DefaultErrorHandler for all error responses.

Callers that supply a custom [Config.ErrorHandler] may use any response shape they prefer.

type HTTPStatuser

type HTTPStatuser interface {
	StatusCode() int
}

HTTPStatuser is an optional interface errors can implement to supply their own HTTP status code. DefaultErrorHandler checks for this before falling back to 500.

Example implementation:

type NotFoundError struct{ Resource string }
func (e *NotFoundError) Error() string      { return e.Resource + " not found" }
func (e *NotFoundError) StatusCode() int    { return http.StatusNotFound }

type Logger

type Logger interface {
	Info(msg string, args ...any)
	Error(msg string, args ...any)
}

Logger is satisfied by log/slog.Logger and most structured loggers.

type Option

type Option func(*Component)

Option configures a Component.

func WithLogger

func WithLogger(l Logger) Option

WithLogger attaches a structured logger to the component. log/slog.Logger satisfies Logger directly.

func WithName

func WithName(name string) Option

WithName overrides the component name returned by Component.Name.

func WithSwagger

func WithSwagger(cfg SwaggerConfig) Option

WithSwagger enables the Swagger UI at [SwaggerConfig.UIPath] and serves the swagger.json spec from [SwaggerConfig.JSONPath].

The UI is mounted as a standard RegisterFunc, so it participates in the normal route registration lifecycle and is available immediately if the component is already running.

Example:

srv := fiber.New(cfg)
fiber.WithSwagger(fiber.SwaggerConfig{
    JSONPath: "./docs/swagger.json",
})(srv)

The UI is then available at http://host:port/api/docs.

type RegisterFunc

type RegisterFunc func(r gf.Router)

RegisterFunc is a callback that receives the root gf.Router (scoped to [Config.PathPrefix]) and registers routes and sub-group middleware on it. All RegisterFuncs are called in registration order during Component.Start, after the built-in middleware stack is applied.

type Route

type Route struct {
	Method string
	Path   string
}

Route identifies an HTTP endpoint by method and path. Used with ExcludeRoutes.

type SkipperFunc

type SkipperFunc func(c gf.Ctx) bool

SkipperFunc is a predicate that returns true when a middleware should be skipped for the current request. Used with ExcludeRoutes.

func ExcludeRoutes

func ExcludeRoutes(routes ...Route) SkipperFunc

ExcludeRoutes returns a SkipperFunc that skips middleware for all routes except those in the allow-list. Useful for applying middleware to most routes while excluding a specific set (e.g. public endpoints).

Example — apply auth middleware to everything except /health and /metrics:

skipper := fiber.ExcludeRoutes(
    fiber.Route{Method: "GET", Path: "/api/health"},
    fiber.Route{Method: "GET", Path: "/api/metrics"},
)
srv.Use(func(c gf.Ctx) error {
    if skipper(c) {
        return c.Next()
    }
    return authMiddleware(c)
})

type SwaggerConfig

type SwaggerConfig struct {
	// JSONPath is the filesystem path to the swagger.json file to serve.
	// Example: "./docs/swagger.json"
	JSONPath string

	// UIPath is the URL path under [Config.PathPrefix] where the Swagger UI
	// is mounted. Defaults to "/docs".
	UIPath string
}

SwaggerConfig configures the optional Swagger UI integration. Pass to WithSwagger to enable it.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL