fastecho

package module
v0.16.1 Latest Latest
Warning

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

Go to latest
Published: Jul 9, 2026 License: Apache-2.0 Imports: 31 Imported by: 0

README

Fastecho

Fastecho is a Go library that provides an easily configurable, ready-to-use echo server. It is a wrapper on top of the echo framework and it adds extra functionalities that are often required when setting up web servers.

Getting started

For specifics, check the detailed features below.

// load env vars
err := envs.SetEnv()
if err != nil {
    log.Fatalf("failed to set environment variables: %s", err)
}

// set up a DB and pass it to handler as you like
// optionally you can provide a gorm config to customize your gorm instance
db, err := fastecho.NewDB(&gorm.Config{
    Logger: newLogger,
})
if err != nil {
    log.Fatalf("failed to connect to the database: %s", err)
}

config := fastecho.Config{
    ExtraEnvs:           envs,
    ValidationRegistrar: validator.RegisterValidations,
    Routes: func(e *echo.Echo, r *router.Router) error {
        return configureRoutes(e, r, db)
    },
    Opts: fastecho.Opts{
        Tracing: fastecho.TracingOpts{
            Skip: !envs["OTEL_ENABLED"].BooleanValue,
        },
        Metrics: fastecho.MetricsOpts{
            Skip: !envs["OTEL_ENABLED"].BooleanValue,
        },
        HealthChecks: fastecho.HealthChecksOpts{
            Skip: false,
            DB:   db,
        },
    },
}

// Starting service...
if err := fastecho.Run(&config); err != nil {
    log.Fatalf("Service stopped! \n %s", err)
}

Features

Logger

We integrated go.uber.org/zap

Request context

Fastecho injects a request-scoped logger, tracer, and request ID into the request's context.Context. Access them via the fctx package:

import "github.com/ingka-group/fastecho/fctx"

func (h *Handler) GetData(ec echo.Context) error {
    ctx := fctx.From(ec)
    log := fctx.Logger(ctx)
    reqID := fctx.RequestID(ctx)

    log.Info("handling request", zap.String("request_id", reqID))
    // ...
}

The logger automatically includes trace_id, span_id, and request_id fields when available.

trace_id and request_id answer different questions: a trace covers a whole user action, while a request id names exactly one HTTP call within it — a page load fanning out to five API calls shares one trace_id, but each call gets its own request_id, echoed back in the X-Request-Id response header so a failing call can be reported and grepped for. See Correlation IDs for a worked example.

Routing

The router provides preset endpoints for swagger, monitoring and health checks. Custom endpoints can be injected via the Routes function in the config.

func configureRoutes(e *echo.Echo, r *router.Router, db *gorm.DB) error {
	myHandler := NewHandler(db)

	v1 := e.Group("/v1")
	myGroup := v1.Group("/example")

	router.AddRoute(r, myGroup, "/data", myHandler, http.MethodGet)
	return nil
}
Request validation

Custom validation can be registered using the provided validator. Define a function in which you register custom validations and add it to the config.

func RegisterValidations(validator *router.Validator) error {
	validator.Vdt.RegisterStructValidation(daterange.ValidateBasicDateRange(), daterange.BasicDateRange{})

	return nil
}
Middleware

Custom middleware can be injected via the route configuration function.

func configureRoutes(e *echo.Echo, r *router.Router, db *gorm.DB) error {
	v1 := e.Group("/v1")
	myGroup := v1.Group("/data")

	myGroup.Use(middleware.MyCustomMiddleware())

	return nil
}
Swagger

Swagger is baked into the router wrapper. The title and JSON path can be configured via environment variables:

SWAGGER_UI_TITLE SWAGGER_JSON_PATH

The swagger documentation is configured on the root path suffixed with /swagger/.

Health probe endpoints

The health endpoints are configured on the root path suffixed with /health/live and /health/ready.

Environment variables

Environment variables are read by default from the environment or from a .env file in the root of the directory.

Fastecho defines the following env vars internally:

Variable Default Notes
HOSTNAME localhost Server hostname
PORT 8080 Server port
SWAGGER_UI_TITLE FastEcho Service Title for Swagger UI
SWAGGER_JSON_PATH /swagger/swagger.json Path to swagger.json
LOG_LEVEL dev One of: dev, test, prod

You can define additional env vars via ExtraEnvs in the config. These are merged with the defaults and loaded automatically when Run() or Initialize() is called:

var envs = env.Map{
	"OTEL_ENABLED": {
		DefaultValue: "false",
		IsBoolean:    true,
	},
}

// load them
err := envs.SetEnv()
if err != nil {
    log.Fatalf("failed to set environment variables: %s", err)
}

config := fastecho.Config{
	ExtraEnvs: envs,
	// ...
}
Observability (OpenTelemetry)

fastecho emits traces + metrics through OpenTelemetry from one shared resource and one shutdown. Logs stay on zap (stdout JSON) and carry trace_id/span_id/request_id so they correlate without a log exporter.

New here? The observability guide explains how traces, metrics, and logs tie together (correlation IDs, pull vs push, exemplars) and what happens under the hood.

Behaviour is configured by standard OTEL_* env vars (read by the OTel SDK and autoexport); fastecho.Opts only carries on/off toggles.

Env vars
Variable Default Effect
OTEL_SERVICE_NAME (unset) service.name on every span/metric; fastecho warns if unset while a signal is on
OTEL_RESOURCE_ATTRIBUTES (unset) Extra resource attrs, e.g. service.version=1.2.3,deployment.environment=prod
OTEL_TRACES_EXPORTER otlp otlp | console | none
OTEL_METRICS_EXPORTER prometheus prometheus (served at /metrics) | otlp (push) | none
OTEL_EXPORTER_OTLP_ENDPOINT localhost:4317 Collector endpoint. TLS by default; use an http:// URL for a plaintext collector
OTEL_EXPORTER_OTLP_PROTOCOL grpc (fastecho default) Set http/protobuf for a :4318 collector
OTEL_TRACES_SAMPLER parentbased_always_on e.g. parentbased_traceidratio
OTEL_TRACES_SAMPLER_ARG 1.0 Sample ratio for the ratio samplers
OTEL_METRICS_EXEMPLAR_FILTER trace_based Which measurements carry trace exemplars

/metrics stays on the service's main port (default prometheus exporter) and keeps serving everything on prometheus.DefaultGatherer — your prometheus.MustRegister metrics and the go_*/process_* collectors — alongside the OTel output. The metric is http_server_request_duration_seconds_* (was echo_http_*).

Code toggles (fastecho.Opts)
Field Effect
Opts.Tracing.Skip Disable tracing
Opts.Metrics.Skip Disable metrics (and /metrics)
Opts.Logs.Skip Disable the access-log middleware

There are intentionally no code fields mirroring an env var (sampler ratio, exporter, endpoint): one source of truth per setting.

Libraries
Concern Package
SDK + API go.opentelemetry.io/otel
HTTP server spans + metrics otelecho
Env-driven exporters autoexport
Go runtime metrics instrumentation/runtime
Outbound client transport otelhttp
/metrics exporter exporters/prometheus
Semantic conventions semconv/v1.41.0

Full env reference: OpenTelemetry SDK environment variables.

Instrumentation coverage
Layer How Effort
HTTP requests Automatic via middleware Zero config
Database (GORM) gorm.io/plugin/opentelemetry Add plugin yourself
Service functions telemetry.StartSpan(ctx) 2 lines
Functions without ctx telemetry.SpanFunc(ctx, name, fn) 3 lines
Outbound HTTP telemetry.WrapClient(c) Wrap your client

Per-function tracing:

import "github.com/ingka-group/fastecho/telemetry"

func (s *Service) Process(ctx context.Context, input Input) error {
    ctx, span := telemetry.StartSpan(ctx)
    defer span.End()
    // span name auto-discovered: "mypackage.Service.Process"
    return s.repo.Save(ctx, input)
}

Tracing a function without context:

var result T
telemetry.SpanFunc(ctx, "heavy-algorithm", func() {
    result = computeHeavyAlgorithm(data)
})

Outbound HTTP calls:

client := telemetry.WrapClient(&http.Client{Timeout: 5 * time.Second})
// client injects traceparent + X-Request-Id on every outbound request
resp, err := client.Do(req.WithContext(ctx))

For database tracing, add gorm.io/plugin/opentelemetry to your project:

import "gorm.io/plugin/opentelemetry/tracing"

_ = db.Use(tracing.NewPlugin())

Important: Always pass context to GORM queries (db.WithContext(ctx).Find(...)) so DB spans attach to the request trace.

Upgrading

Upgrading from an earlier fastecho version? See CHANGELOG.md for the full list of breaking changes and migration steps.

Database (optional)

Fastecho has an optional postgres DB connection baked into it using gorm. We are using goose for migrations rather than gorm Automigrate. The migrations are expected to be under db/migrations in the root of your folder.

Background workers

Services often need long-running background processes (e.g. a Pub/Sub listener) that share the server's lifecycle. Register them via Workers in the config and fastecho manages their full lifecycle: each worker runs in its own goroutine, receives a context that is cancelled on shutdown, and is waited for while the server drains. The drain shares the 10s graceful-shutdown budget with the HTTP server, so a worker is not guaranteed a full 10s to finish if HTTP draining is slow.

A Worker is a plain function that should block until its context is cancelled:

config := fastecho.Config{
	Routes: configureRoutes,
	Workers: map[string]fastecho.Worker{
		"pubsub-listener": func(ctx context.Context) error {
			return pubsub.StartListener(ctx, "project", "subscription")
		},
	},
}

Workers are supervised: since a worker should block until its context is cancelled, any early return (error, nil, or a recovered panic) is treated as a fault and the worker is restarted with exponential backoff (1s, doubling, capped at 30s; reset after 1m stable). Restarts are logged at warning level; once a worker has restarted 10 times without a stable run the log escalates to error, so a persistent crash loop can be distinguished from an occasional restart by anything consuming the logs. Only a context-cancelled return is a clean stop — no restart, no error log. Other workers and the HTTP server are unaffected.

Plugins

Plugins are a set of handlers and their bound components (validators, middlewares, etc) which can be reused across multiple services using fastecho.

fastechoConfig.Use(<pluginConfig>)

Access Echo instance

The underlying Echo instance can be accessed by passing the value for EchoFn in the config. This is useful for binding service level middlewares, enabling echo's debug mode or any other Echo features.

config := fastecho.Config{
	ValidationRegistrar: func(*router.Validator) error {
		return nil
	},
	Routes: func(e *echo.Echo, r *router.Router) error {
		return configureRoutes(e, r, db)
	},
	EchoFn: func(e *echo.Echo) error {
		// Use a service level middleware
		e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
			AllowOrigins:     []string{"https://example.com"},
			AllowHeaders:     []string{echo.HeaderOrigin, echo.HeaderContentType, echo.HeaderAccept, echo.HeaderAuthorization},
			MaxAge:           3600,
			AllowCredentials: true,
			ExposeHeaders:    []string{echo.HeaderContentLength},
		}))
		// Enable debug mode in echo
		e.Debug = true
		return nil
	},
	...
}

Miscellaneous

Fastecho is fully compatible with Echoprobe

Contributing

Please read CONTRIBUTING for more details about making a contribution to this open source project and ensure that you follow our CODE_OF_CONDUCT.

Contact

If you have any other issues or questions regarding this project, feel free to contact one of the code owners/maintainers for a more in-depth discussion.

Licence

This open source project is licensed under the "Apache-2.0", read the LICENCE terms for more details.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func BindValidate added in v0.13.0

func BindValidate(ec echo.Context, v any) error

BindValidate binds the request body to v and validates it using the registered validator. Use this at the handler boundary.

func NewDB

func NewDB(cfg *gorm.Config) (*gorm.DB, error)

NewDB creates a new *gorm.DB configured through the DB_* environment variables. It also runs any goose migrations found under db/migrations before returning.

func Run

func Run(cfg *Config) error

Run starts a new instance of fastecho.

Types

type Config

type Config struct {
	// ExtraEnvs declares additional env vars to resolve at boot. A key matching
	// one of fastecho's own vars replaces that declaration entirely, default
	// included.
	ExtraEnvs           env.Map
	ValidationRegistrar func(v *router.Validator) error
	Routes              func(e *echo.Echo, r *router.Router) error
	// ContextProps would be shared across all requests in the service
	ContextProps any
	Opts         Opts
	Plugins      []Plugin
	EchoFn       func(e *echo.Echo) error
	// Workers are long-running background processes managed by fastecho's
	// lifecycle, keyed by name. Each runs in its own goroutine with a context that
	// is cancelled on shutdown; its logs/spans are tagged worker=<name>.
	Workers map[string]Worker
}

Config serves as input configuration for fastecho.

func (*Config) Use added in v0.1.0

func (c *Config) Use(p Plugin)

type FastEcho

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

func Initialize

func Initialize(cfg *Config) (*FastEcho, error)

Initialize sets up a new instance of FastEcho and returns a prepared FastEcho type, but does not boot the server.

func (*FastEcho) Handler

func (fe *FastEcho) Handler() http.Handler

Handler returns the Echo handler for the defined FastEcho server.

func (*FastEcho) Shutdown

func (fe *FastEcho) Shutdown(ctx context.Context) error

Shutdown cleanly shuts down the server and any telemetry providers. Both are always attempted and their errors joined - a drain error must not skip the telemetry flush, which exports the spans of the requests just drained.

type HealthChecksOpts

type HealthChecksOpts struct {
	Skip bool     `json:"skip"`
	DB   *gorm.DB `json:"db,omitempty"`
}

HealthChecksOpts define configuration options for health checks.

type LogsOpts added in v0.16.0

type LogsOpts struct {
	Skip bool `json:"skip"`
}

LogsOpts define configuration options for logging.

type MetricsOpts

type MetricsOpts struct {
	Skip bool `json:"skip"`
}

MetricsOpts define configuration options for metrics.

type Opts

type Opts struct {
	Metrics      MetricsOpts      `json:"metrics"`
	Tracing      TracingOpts      `json:"tracing"`
	Logs         LogsOpts         `json:"logs"`
	HealthChecks HealthChecksOpts `json:"health_checks"`
}

Opts define configuration options for fastecho.

type Plugin added in v0.1.0

type Plugin struct {
	ValidationRegistrar func(v *router.Validator) error
	Routes              func(e *echo.Echo, r *router.Router) error
}

type TracingOpts

type TracingOpts struct {
	Skip bool `json:"skip"`
}

TracingOpts define configuration options for tracing.

type Worker added in v0.14.0

type Worker func(ctx context.Context) error

Worker is a long-running background process managed by fastecho's lifecycle. It should block until ctx is canceled, returning ctx.Err() (or nil) on shutdown.

Directories

Path Synopsis
internal
banner
Package banner prints the startup banner sections, so every section of the boot output shares one format and column width.
Package banner prints the startup banner sections, so every section of the boot output shares one format and column width.
version
Package version reports fastecho's own module version.
Package version reports fastecho's own module version.

Jump to

Keyboard shortcuts

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