fiberfx

package module
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Oct 25, 2026 License: Apache-2.0 Imports: 16 Imported by: 1

README

Contributors Forks Stargazers Issues Apache 2.0 License


fiberfx

Uber Fx module for Fiber v3 — HTTP server with structured logging, health probes, Prometheus metrics, OpenAPI docs and request validation.

Report Bug · Request Feature

Table of Contents


About The Project

fiberfx is an Uber Fx module that builds and runs a Fiber v3 HTTP server. It wires the server into the Fx lifecycle — the listener starts on OnStart and shuts down gracefully on OnStop — and ships the cross-cutting pieces most services end up writing by hand:

  • Lifecycle-managed server — Module() provides *fiber.App, opens the listener on the Fx start hook and drains connections on shutdown
  • Structured request logging — request ID, latency, status, method, URL, IP, user agent and error fields, rendered through zap
  • JSON error contract — one ErrorResponse shape for every error the server returns
  • Health probes — liveness, readiness and startup endpoints backed by healthfx
  • Prometheus metrics — opt-in /metrics endpoint, on an injected registry or the process-wide default
  • OpenAPI documentation — Swagger UI and doc.json served from the generated spec
  • Request validation — struct-tag validation with per-field error details and custom Validate() hooks

This is a library, not an application: it reads no environment variables and has no configuration file. Everything is configured through Go structs (fiberfx.Config, fiberfx.Options and the per-feature config types), which the host application supplies to the Fx graph.

Features

  • Fiber v3 — built on github.com/gofiber/fiber/v3
  • Uber Fx module — fiberfx.Module() provides *fiber.App and manages the listener lifecycle
  • Graceful shutdown — app.ShutdownWithContext(ctx) on the Fx stop hook; a listener failure triggers fx.Shutdown
  • Request ID propagation — the X-Request-ID response header is set per request and logged
  • Panic recovery — the Fiber recover middleware is installed by default
  • IP validation — enabled by default, so c.IP() returns a single validated address instead of a raw X-Forwarded-For chain
  • Always-unescaped paths — request paths are unescaped before routing
  • Proxy trust — optional trusted-proxy list and proxy header
  • Full Fiber configuration — any fiber.Config field (error handler, template engine, body limit, GET-only mode) can be overridden through a callback
  • Configurable log fields — the request log field set can be replaced
  • Health, metrics and OpenAPI — the three operational endpoints every service needs

Built With

  • Go
  • Uber Fx
  • Fiber
  • Zap
  • Prometheus

(back to top)


Getting Started

Prerequisites

Installation

go get github.com/go-core-fx/fiberfx@latest

(back to top)


Usage

Module Setup

fiberfx.Module() provides *fiber.App. The graph must also contain fiberfx.Config, fiberfx.Options and a base *zap.Logger; a *promclient.Registry is optional and only needed for a custom metrics registry.

package main

import (
	"github.com/go-core-fx/fiberfx"
	"github.com/gofiber/fiber/v3"
	"go.uber.org/fx"
	"go.uber.org/zap"
)

func main() {
	fx.New(
		// Base logger. The module decorates it with the name "http".
		fx.Provide(zap.NewProduction),

		fx.Provide(func() fiberfx.Config {
			return fiberfx.Config{
				Address: ":8080",
			}
		}),

		fx.Provide(func(logger *zap.Logger) fiberfx.Options {
			var opts fiberfx.Options
			opts.WithFiberConfig(func(cfg *fiber.Config) {
				cfg.ErrorHandler = fiberfx.NewJSONErrorHandler(logger)
			})
			opts.WithMetrics()
			return opts
		}),

		fiberfx.Module(),
	).Run()
}

Options is configured through pointer receivers, so build it as a local variable and supply the value:

opts := fiberfx.Options{}
opts.WithMetrics()

fx.Supply(opts)

The module starts the listener on the Fx start hook and shuts it down on the stop hook, logging starting server, server listening, shutting down server and server shutdown completed through the http logger.

The *fiber.App is available for route registration, either by injecting it or by populating it from the graph:

var app *fiber.App

fx.New(
	// ...
	fiberfx.Module(),
	fx.Populate(&app),
).Run()

Configuration Reference

Field Type Default Description
Address string — Listen address passed to net.Listen (for example ":8080" or "127.0.0.1:8080"). No default.
ProxyHeader string "" Request header holding the client IP. Only used when Proxies is non-empty.
Proxies []string nil Trusted proxy IPs or CIDR ranges. When empty, proxy headers are ignored entirely.

Proxy trust is enabled only when Proxies is non-empty — with an empty list the client IP is taken from the remote address and ProxyHeader has no effect.

Options

Option Effect
WithFiberConfig(configure) Applies changes to the Fiber configuration after the package defaults — see Fiber Configuration.
WithRequestLogFields(fields...) Replaces the fields written to the request log. An empty list keeps the defaults — see Request Logging.
WithMetrics() Mounts the Prometheus metrics endpoint at /metrics.

Fiber Configuration

WithFiberConfig hands the assembled fiber.Config to a callback, so anything Fiber supports can be overridden — the error handler, the template engine, the body limit, the read/write timeouts and so on:

opts := fiberfx.Options{}
opts.WithFiberConfig(func(cfg *fiber.Config) {
	cfg.ErrorHandler = fiberfx.NewJSONErrorHandler(logger)
	cfg.Views = views
	cfg.BodyLimit = 30 * 1024 * 1024
	cfg.GETOnly = true
})

The callback runs after the package defaults are applied (EnableIPValidation, the trusted proxies from fiberfx.Config and UnescapePath), so it can override them.

Request Logging

Every request is logged with requestId, latency, status, method, url, ip, ua, body and error. WithRequestLogFields replaces that list; an empty list keeps the defaults:

opts := fiberfx.Options{}
opts.WithRequestLogFields("requestId", "status", "error")

Health and metrics paths are never logged.

Error Handling

All errors the server returns use one shape:

{
  "message": "Not Found",
  "code": 404
}

message and code are always present; details is optional and carries structured context when there is any.

Three handlers are provided, and are installed through WithFiberConfig:

// JSON error response — the default choice for APIs.
fiberfx.NewJSONErrorHandler(logger)

// JSON error response with a custom payload.
fiberfx.NewCustomJSONErrorHandler(logger, func(err error, code int) any {
	return fiberfx.NewErrorResponse("something went wrong", code, nil)
})

// HTML error page rendered through a fiber.Views engine.
fiberfx.NewViewsErrorHandler(logger, "error", "layouts/main")

Status codes are derived from the error:

Source Status
*fiber.Error the error's own code
context.Canceled 499 (fiberfx.StatusClientClosedRequest)
context.DeadlineExceeded 408
anything else 500

For a 4xx the raw error message is returned, except for 408 and 499 which get a fixed text; for a 5xx the standard status text is returned instead, so internal details never leak to clients. Errors with a 5xx status are logged through the logger that was passed to the handler.

499 is non-standard but widely used. If your infrastructure only accepts standard codes, map fiberfx.StatusClientClosedRequest to fiber.StatusRequestTimeout in a custom handler.

Health Probes

The health package exposes liveness, readiness and startup probes backed by healthfx:

app := fiberfx.New(cfg, opts, registry, logger)

// /health, /health/live, /health/ready, /health/startup
health.NewHandler(healthSvc).Register(app)
Route Meaning
/health alias of /health/live
/health/live the process is running
/health/ready the process can serve traffic
/health/startup the process finished initializing

A failed probe responds 503, a passing one 200:

{
  "status": "pass",
  "version": "1.0.0",
  "releaseId": 42,
  "checks": {
    "system:goroutines": {
      "description": "Number of goroutines",
      "observedUnit": "goroutines",
      "observedValue": 12,
      "status": "pass"
    }
  }
}

version and releaseId come from the healthfx.Version held by the health service. Check names are prefixed with the provider name. The overall status is pass, warn or fail; only fail maps to 503.

Prometheus Metrics

WithMetrics() mounts the Fiber Prometheus middleware at /metrics:

opts := fiberfx.Options{}
opts.WithMetrics()

Metrics land in the process-wide default registry unless a *promclient.Registry is present in the graph:

fx.Provide(func() *promclient.Registry {
	return promclient.NewRegistry()
})

An injected registry starts empty, so the middleware registers the Go and process collectors into it itself; the default registry already contains them. The exposed families are http_requests_total, http_requests_status_class_total, http_request_duration_seconds, http_request_size_bytes, http_response_size_bytes and http_requests_in_progress.

OpenAPI Documentation

The openapi package serves a Swagger UI and the generated spec from a swag.Spec:

spec := &swag.Spec{
	SwaggerTemplate: `{
		"swagger": "2.0",
		"info": {"title": "My API", "version": "{{.Version}}"},
		"host": "{{.Host}}",
		"basePath": "{{.BasePath}}",
		"paths": {}
	}`,
}
swag.Register(swag.Name, spec)

openapi.NewHandler(
	openapi.Config{
		Enabled:    true,
		PublicHost: "api.example.com",
		PublicPath: "/v1",
	},
	healthfx.Version{Version: "1.0.0", ReleaseID: 1},
	spec,
).Register(app.Group("/swagger"))
Field Default Description
Enabled false When false the handler registers no routes at all.
PublicHost "" Host written into the spec. When empty, the host of the first request is used.
PublicPath "" Base path written into the spec. When empty, the base path from the spec is kept.

With the group mounted at /swagger, the UI is served at /swagger/index.html and the spec at /swagger/doc.json. Responses carry a weak ETag.

Request Validation

The validation package validates request bodies and returns per-field error details:

type CreateUserRequest struct {
	Name  string `json:"name"  validate:"required"`
	Email string `json:"email" validate:"required,email"`
}

app.Post("/users",
	validation.Middleware,
	validation.NewBody[CreateUserRequest](validator.New()),
	validation.DecorateWithBody(func(c fiber.Ctx, body *CreateUserRequest) error {
		return c.Status(fiber.StatusCreated).JSON(body)
	}),
)

validation.Middleware converts a validation failure into a 400 response:

{
  "message": "validation failed: name: name is required",
  "code": 400,
  "details": [
    {
      "field": "name",
      "tag": "required",
      "message": "name is required"
    }
  ]
}
Function Purpose
validation.Middleware Middleware that renders validation failures as a 400 JSON response.
validation.NewBody[T](validate) Validates the body and stores the result in the request context.
validation.ValidateBody[T](c, validate) Validates the body and returns it directly.
validation.GetValidatedBody[T](c) Reads the validated body stored by NewBody.
validation.DecorateWithBody[T](h) Wraps a handler so it receives the validated body.
validation.DecorateWithBodyEx[T](validate, h) Validates and wraps in one step, without the middleware.

A request type may implement Validate() error for rules that struct tags cannot express. It runs after the tag validation and its error is reported the same way:

func (r *CreateUserRequest) Validate() error {
	if r.Name == "admin" {
		return errors.New("name is reserved")
	}
	return nil
}

Handler Base Helpers

handler.Base binds and validates a request in one call. It is useful when a route needs the parsed values directly instead of a validated body in the context:

base := &handler.Base{Validator: validator.New()}

app.Get("/users/:id", func(c fiber.Ctx) error {
	var out struct {
		ID string `uri:"id" validate:"required"`
	}
	if err := base.ParamParserValidator(c, &out); err != nil {
		return err
	}
	return c.SendString(out.ID)
})
Method Binds from Struct tag (Fiber v3) Fiber v2 tag
BodyParserValidator(c, out) request body json json
QueryParserValidator(c, out) query string query query
ParamParserValidator(c, out) route parameters uri params
HeaderParserValidator(c, out) request headers header reqHeader

The Fiber v3 binder reads uri and header; the v2 tag names are not read.

JSON Content Type

jsonify makes responses advertise application/json when the client accepts JSON and the body is empty or already valid JSON. Bodies that are not valid JSON are left untouched:

app.Use(jsonify.New())

Status Code Middleware

statuscode answers with a fixed status code and message, for example to return 404 for unmatched routes instead of Fiber's default response:

app.Use(statuscode.New(statuscode.Config{
	StatusCode:    fiber.StatusNotFound,
	StatusMessage: "Not Found",
}))

Both fields are optional; they default to 404 and Not Found. Next skips the middleware for matching requests:

app.Use(statuscode.New(statuscode.Config{
	Next: func(c fiber.Ctx) bool {
		return c.Path() == "/health"
	},
}))

(back to top)


Roadmap

  • Fiber v3 server wired into the Fx lifecycle
  • Graceful shutdown
  • Structured request logging with request ID
  • Configurable request log fields
  • JSON error contract and customizable error handlers
  • Health probes (liveness, readiness, startup)
  • Prometheus metrics with an optional injected registry
  • OpenAPI / Swagger UI
  • Request validation with per-field errors
  • Trusted proxy support
  • Full Fiber configuration override

See the open issues for a full list of proposed features.

(back to top)


Contributing

Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated.

  1. Fork the Project
  2. Create your Feature Branch (git checkout -b feature/AmazingFeature)
  3. Commit your Changes (git commit -m 'Add some AmazingFeature')
  4. Push to the Branch (git push origin feature/AmazingFeature)
  5. Open a Pull Request

(back to top)


License

Distributed under the Apache License 2.0. See LICENSE for more information.

(back to top)


Acknowledgments

(back to top)

Documentation

Index

Constants

View Source
const StatusClientClosedRequest = 499

StatusClientClosedRequest is the HTTP status code for client closed request errors.

Variables

This section is empty.

Functions

func Module

func Module() fx.Option

func New

func New(config Config, option Options, registry *promclient.Registry, logger *zap.Logger) *fiber.App

func NewCustomJSONErrorHandler

func NewCustomJSONErrorHandler(logger *zap.Logger, formatter ErrorFormatter) fiber.ErrorHandler

NewCustomJSONErrorHandler returns a fiber.ErrorHandler that formats the given error and HTTP status code using the provided ErrorFormatter, and sends the result as a JSON response. If the formatter is nil, it falls back to using NewErrorResponse to format the error.

func NewJSONErrorHandler

func NewJSONErrorHandler(logger *zap.Logger) fiber.ErrorHandler

NewJSONErrorHandler returns a fiber.ErrorHandler that formats the given error and HTTP status code using the NewErrorResponse function, and sends the result as a JSON response.

func NewViewsErrorHandler

func NewViewsErrorHandler(logger *zap.Logger, template string, layouts ...string) fiber.ErrorHandler

NewViewsErrorHandler creates a fiber.ErrorHandler that renders the given template with the error message and status code. If rendering fails, it falls back to sending the error message as a plain-text response. The logger is used to log errors that occur while rendering the view. The layouts are passed to the Render method as is.

Types

type Config

type Config struct {
	Address     string
	ProxyHeader string
	Proxies     []string
}

type ErrorFormatter

type ErrorFormatter func(err error, code int) any

ErrorFormatter formats an error and HTTP status code into a JSON-serializable payload.

type ErrorResponse

type ErrorResponse struct {
	Message string `json:"message"`
	Code    int    `json:"code,omitempty"`
	Details any    `json:"details,omitempty"`
}

func NewErrorResponse

func NewErrorResponse(message string, code int, details any) ErrorResponse

type Options

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

func (*Options) WithFiberConfig added in v1.0.0

func (o *Options) WithFiberConfig(configure func(*fiber.Config)) *Options

WithFiberConfig applies changes after the default Fiber config is assembled.

func (*Options) WithMetrics added in v0.1.0

func (o *Options) WithMetrics() *Options

func (*Options) WithRequestLogFields added in v1.0.0

func (o *Options) WithRequestLogFields(fields ...string) *Options

WithRequestLogFields replaces the HTTP logger fields; an empty list uses the defaults.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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