credo

package module
v0.0.0-...-927d699 Latest Latest
Warning

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

Go to latest
Published: Jun 17, 2026 License: MIT Imports: 41 Imported by: 0

README

Credo

An all-in-one Go web framework for modern, enterprise-grade applications.

Go Version License Go Report Card GoDoc

Status: Beta -- Core APIs are stable enough for real application development. Breaking changes may still happen before v1, but should be documented with migration notes. Feedback from active pilot projects continues to shape the framework.

What Credo Optimizes For

  • All-in-one: router, DI, config, validation, observability, data access -- one cohesive framework.
  • Enterprise defaults: Clean Architecture is the recommended path (CLI scaffolding + docs), but not enforced.
  • Errors are values: handlers return error; centralized error handling renders RFC 7807 Problem Details.
  • Typed config snapshot: config is loaded once at startup and injected as typed structs via DI.
  • Explicit-first (DX-balanced): business deps are normal constructor params; infra comes via credo.Infra.
  • stdlib boundary compatibility: *credo.App is an http.Handler; stdlib middleware can be adapted.

Maturity by Area

Credo is Beta overall. Shipped packages are usable for real development; the table below is explicit about what is solid, what is still experimental, and what is on the roadmap.

Area Status
Routing, Context, Handlers, Middleware Beta
Dependency Injection (Provide/Resolve, Infra) Beta
Configuration (config, typed snapshot) Beta
Validation Beta
Authentication (JWT / Basic / API key) Beta
Internationalization (i18n) Beta
Health checks Beta
Data access — contracts (store) Beta
Data access — Bun SQL wrapper + migrations (store/sqldb) Beta
Background workers + cron (worker) Beta
Pagination Beta
Outbound HTTP client (httpclient) Beta
Observability — structured logging (slog via Infra) Beta
Observability — tracing (OpenTelemetry) Experimental
Observability — metrics (Prometheus) Experimental
pubsub (incl. in-process events) · grpc · websocket · openapi · admin server · CLI Planned

Installation

go get github.com/credo-go/credo@latest

Requires Go 1.26+. Credo tracks the current Go release to build on the modern standard library (e.g. os.Root, structured log/slog). It targets new and actively-maintained services rather than legacy codebases pinned to older toolchains — enterprise-grade in capability, modern in its baseline.

Quick Start (Target API)

package main

import (
    "log"

    "github.com/credo-go/credo"
)

func main() {
    // Create the app (auto-loads config from files, .env, and env vars).
    app, err := credo.New()
    if err != nil {
        log.Fatal(err)
    }

    app.GET("/", func(ctx *credo.Context) error {
        return ctx.Response().JSON(200, map[string]string{"message": "Hello, Credo!"})
    })

    // Server settings come from framework-internal server config.
    // Example: set `CREDO_SERVER__PORT=8080`.
    if err := app.Run(); err != nil {
        log.Fatal(err)
    }
}

Config: Typed Snapshot (Anti-Pattern-Free)

Credo positions config as a startup-time snapshot, not a runtime service. String keys should appear only at module boundaries; everything beyond that boundary is typed.

type DatabaseConfig struct {
    DSN string `credo:"dsn"`
}

func SetupDatabase(app *credo.App, store credo.RawConfig) error {
    var cfg DatabaseConfig
    if err := store.Unmarshal("databases.default", &cfg); err != nil {
        return err
    }
    return credo.ProvideValue(app, &cfg)
}

Dependency Injection

Credo uses generics-based DI. Cross-cutting infrastructure is carried explicitly via credo.Infra; business dependencies (including typed config) are normal constructor parameters. Single-interface wiring uses Alias[I, T], and ordered interface collections use BindMany[I, T] + ResolveAll[I] or []I constructor injection.

func NewOrderService(infra credo.Infra, cfg *OrderConfig, repo OrderRepo) *OrderService {
    infra.Logger.Info("order service initialized")
    return &OrderService{cfg: cfg, repo: repo}
}

Documentation

  • User guide: docs/guides/getting-started.md
  • User guide: docs/guides/routing.md
  • User guide: docs/guides/middleware.md
  • User guide: docs/guides/proxy-trust.md
  • User guide: docs/guides/dependency-injection.md
  • User guide: docs/guides/data-access.md
  • User guide: docs/guides/configuration.md
  • User guide: docs/guides/localization.md
  • Architecture decisions: docs/adr/
  • Detailed specs: docs/specs/

Repository Layout (High Level)

  • Root package (github.com/credo-go/credo): App, Context, routing, handler/middleware types
  • config/: config loading; returns credo.RawConfig
  • middleware/, validation/, auth/, store/, ...: feature packages
  • internal/: private implementations (router radix tree, DI internals, etc.)
  • docs/: ADRs (docs/adr/) and detailed specs (docs/specs/)

Contributing

See CONTRIBUTING.md for guidelines on how to contribute.

Security

See SECURITY.md for vulnerability reporting instructions, and SECURITY-UPSTREAMS.md for how Credo tracks the upstream projects its adapted code derives from. Reports are triaged by severity on a best-effort basis — there is no fixed response-time guarantee.

License

MIT -- see LICENSE for details.

Documentation

Overview

Package credo is a batteries-included Go web framework that combines the best patterns from Chi (router), Echo (context), Goyave (architecture & components), and GoFr (enterprise toolkit).

It targets Go 1.26+ and leverages generics for type-safe dependency injection without reflection.

Quick Start

package main

import (
	"log"

	"github.com/credo-go/credo"
)

func main() {
    app, err := credo.New()
    if err != nil {
        panic(err)
    }

    app.GET("/", func(ctx *credo.Context) error {
        return ctx.Response().JSON(200, map[string]string{"message": "Hello, Credo!"})
    })

    if err := app.Run(); err != nil {
        log.Fatal(err)
    }
}

Key Concepts

  • Handler: func(*credo.Context) error — all handlers return errors
  • Context: request-scoped struct with Request/Response accessors
  • Middleware: func(credo.Handler) credo.Handler — wraps Handlers. Four tiers run in order: built-in → global → group → route. Group middleware is collected from the group parent chain when the app compiles, so registration order affects execution order only — middleware added to a group after its routes still applies to them.
  • Route: fluent API with Name(), SetMeta(), Middleware()
  • ErrorRenderer: customizes error response formatting via App.SetErrorRenderer (classification/logging handled by framework)

Panics and Errors

Credo separates developer errors from runtime failures:

  • Startup configuration (registering routes, hosts, middleware, names, static files, health checks) panics on misuse — nil handlers, malformed patterns, duplicates, or registration after the handler chain has compiled. The route table is code written by the developer, so a mistake there is a bug best caught at startup, not a condition to handle.
  • Anything that can legitimately fail at runtime — request handling, server lifecycle, or operations touching the outside world (file I/O, network) — returns an error.

This is why App.UseHealth panics on misuse (it only registers in-process state) while App.UseI18n returns an error (it loads locale files).

Stability

Credo is Beta: shipped packages are usable for real development, with breaking changes possible before v1. See the project README's "Maturity by Area" table for per-area status, including which features are experimental or still planned.

Index

Constants

View Source
const (
	MsgKeyBadRequest          = "http.bad_request"
	MsgKeyUnauthorized        = "http.unauthorized"
	MsgKeyForbidden           = "http.forbidden"
	MsgKeyNotFound            = "http.not_found"
	MsgKeyMethodNotAllowed    = "http.method_not_allowed"
	MsgKeyConflict            = "http.conflict"
	MsgKeyUnprocessableEntity = "http.unprocessable_entity"
	MsgKeyUnsupportedMedia    = "http.unsupported_media_type"
	MsgKeyInternalError       = "http.internal_server_error"
	MsgKeyTooManyRequests     = "http.too_many_requests"
	MsgKeyServiceUnavailable  = "http.service_unavailable"
	MsgKeyGatewayTimeout      = "http.gateway_timeout"
	MsgKeyRequestTimeout      = "http.request_timeout"
	MsgKeyValidationFailed    = "http.validation_failed"
)

MsgKey constants define i18n message keys for standard HTTP errors. These keys are used in locale files (e.g., locales/en/messages.json) and as lookup keys for [builtInMessages].

Variables

View Source
var (
	ErrNotFound             = NewHTTPError(http.StatusNotFound)
	ErrMethodNotAllowed     = NewHTTPError(http.StatusMethodNotAllowed)
	ErrBadRequest           = NewHTTPError(http.StatusBadRequest)
	ErrUnauthorized         = NewHTTPError(http.StatusUnauthorized)
	ErrForbidden            = NewHTTPError(http.StatusForbidden)
	ErrInternalServerError  = NewHTTPError(http.StatusInternalServerError)
	ErrConflict             = NewHTTPError(http.StatusConflict)
	ErrUnprocessableEntity  = NewHTTPError(http.StatusUnprocessableEntity)
	ErrUnsupportedMediaType = NewHTTPError(http.StatusUnsupportedMediaType)
)

Sentinel errors for common HTTP error conditions.

These are shared package-level instances, like io.EOF: compare with errors.Is and treat them as immutable. Mutating a sentinel's fields would silently change the behavior of every handler in the process. To attach context, derive a copy instead — HTTPError.WithInternal for a wrapped cause, or NewHTTPError for a different status or message key.

Functions

func Alias

func Alias[I, T any](app *App) error

Alias creates a type alias so that Resolve[I] returns the singleton registered for concrete type T. I must be an interface, T must implement I, and T must already be registered.

credo.Alias[UserRepo, *PgUserRepo](app)

func BindMany

func BindMany[I, T any](app *App) error

BindMany adds concrete type T to the ordered collection for interface I. I must be an interface, T must be a registered concrete type, and T must implement I.

func DirFS

func DirFS(dir string) (fs.FS, io.Closer, error)

DirFS returns a traversal-safe fs.FS rooted at dir, together with an io.Closer that releases the underlying directory handle.

Unlike os.DirFS, it is backed by os.Root, so symlinks that resolve outside dir are refused — closing a common path-traversal hole when serving files from disk. Prefer it over os.DirFS for disk-backed static serving.

The FS holds an open directory handle until the closer is called. Register the closer for graceful shutdown so the handle is released cleanly:

fsys, closer, err := credo.DirFS("./public")
if err != nil {
	return err
}
app.Static("/assets", fsys)
app.OnShutdown(func(context.Context) error { return closer.Close() })

It returns an error if dir cannot be opened.

func Finalize

func Finalize(app *App) error

Finalize freezes the DI container and validates the dependency graph. After Finalize, no more Provide, ProvideFunc, ProvideValue, Replace, Alias, or BindMany calls are allowed. Finalize is idempotent. If not called explicitly, Run and RunTLS call it implicitly.

credo.Finalize(app)

func MustAlias

func MustAlias[I, T any](app *App)

MustAlias is like Alias but panics on error.

func MustBindMany

func MustBindMany[I, T any](app *App)

MustBindMany is like BindMany but panics on error.

func MustProvide

func MustProvide[T any](app *App, constructor any)

MustProvide is like Provide but panics on error.

func MustProvideFunc

func MustProvideFunc[T any](app *App, fn func(*App) (T, error))

MustProvideFunc is like ProvideFunc but panics on error.

func MustProvideValue

func MustProvideValue[T any](app *App, value T)

MustProvideValue is like ProvideValue but panics on error.

func MustReplace

func MustReplace[T any](app *App, value T)

MustReplace is like Replace but panics on error.

func MustResolve

func MustResolve[T any](app *App) T

MustResolve is like Resolve but panics on error. It is primarily intended for bootstrap/composition-root code.

func MustResolveAll

func MustResolveAll[T any](app *App) []T

MustResolveAll is like ResolveAll but panics on error.

func Provide

func Provide[T any](app *App, constructor any) error

Provide registers a constructor for type T in the application's DI container. The constructor can accept any number of parameters that are themselves registered, and must return T or (T, error).

credo.Provide[*UserService](app, NewUserService)

Because Go cannot express "a function with arbitrary parameters returning T" in the type system, constructor is typed any: signature mistakes (wrong return type, not a function) are reported as an error at registration time, not at compile time. The dependency graph itself is still validated at Finalize. For a constructor checked entirely by the compiler, see ProvideFunc.

func ProvideFunc

func ProvideFunc[T any](app *App, fn func(*App) (T, error)) error

ProvideFunc registers a compile-time-checked constructor for type T. Unlike Provide, whose constructor parameter is typed any and inspected at registration time, fn's signature is enforced by the compiler — and T is inferred from it. fn receives the App and resolves its own dependencies:

credo.ProvideFunc(app, func(app *credo.App) (*UserService, error) {
	repo, err := credo.Resolve[*UserRepository](app)
	if err != nil {
		return nil, err
	}
	return NewUserService(app.NewInfra("UserService"), repo), nil
})

Like Provide, fn runs lazily on first resolution, exactly once, and the instance participates in reverse-order shutdown.

Trade-offs versus Provide:

  • fn is opaque to the container. Dependencies resolved inside fn are not part of Finalize's graph validation — a missing dependency surfaces at first resolution instead. Cycles entered through fn are likewise invisible to the resolver's cycle detection (the same holds for any constructor closure that captures app and calls Resolve).
  • Infra is not auto-injected; use App.NewInfra inside fn as shown.

func ProvideValue

func ProvideValue[T any](app *App, value T) error

ProvideValue registers a pre-built value for type T as a Singleton.

credo.ProvideValue[*Logger](app, logger)

func Replace

func Replace[T any](app *App, value T) error

Replace registers a pre-built value for type T, overwriting any existing registration. Unlike ProvideValue, it does not return an error when T is already registered: it replaces the binding and discards any cached singleton.

Replace is intended for composition-root overrides and tests where a real binding is swapped for a stub or fake. Because the replacement is a value, it carries no dependencies and stays valid during Finalize. Replace is rejected after the container is finalized.

In tests, the github.com/credo-go/credo/testutil package builds on Replace through its WithOverride option.

credo.Replace[UserRepo](app, mockRepo)

func Resolve

func Resolve[T any](app *App) (T, error)

Resolve retrieves an instance of type T from the application's DI container. Resolve is primarily intended for bootstrap/composition-root code; runtime calls remain available, but Credo's recommended application pattern is constructor injection.

svc, err := credo.Resolve[*UserService](app)

func ResolveAll

func ResolveAll[T any](app *App) ([]T, error)

ResolveAll retrieves all singletons bound to interface type T via BindMany, preserving bind order. When no bindings exist, it returns an empty slice and nil error.

func RunTLSWithSignals

func RunTLSWithSignals(app *App, certFile, keyFile string, timeout time.Duration, signals ...os.Signal) error

RunTLSWithSignals starts the HTTPS server and blocks until a signal is received, then initiates graceful shutdown with the given timeout. If no signals are specified, defaults to os.Interrupt and syscall.SIGTERM.

func RunWithSignals

func RunWithSignals(app *App, timeout time.Duration, signals ...os.Signal) error

RunWithSignals starts the HTTP server and blocks until a signal is received, then initiates graceful shutdown with the given timeout. If no signals are specified, defaults to os.Interrupt and syscall.SIGTERM.

This is a convenience wrapper around App.Run + App.Shutdown for the common case. For TLS, use RunTLSWithSignals.

func StaticCacheImmutableAssets

func StaticCacheImmutableAssets(maxAge time.Duration) func(StaticCacheContext) string

StaticCacheImmutableAssets returns a [StaticConfig.CacheControl] hook for content-hashed builds: non-HTML assets get "public, max-age=N, immutable", while HTML responses (index files, SPA fallbacks, prerendered pages, and directory listings) get "no-cache, must-revalidate" so entry points stay refreshable. Panics if maxAge is negative or floors below 1 second.

func StaticCacheMaxAge

func StaticCacheMaxAge(maxAge time.Duration) func(StaticCacheContext) string

StaticCacheMaxAge returns a [StaticConfig.CacheControl] hook that serves every successful response with "public, max-age=N". Sub-second durations are floored to whole seconds; if the floored result is 0 the hook writes no header. Panics if maxAge is negative.

func URLParam

func URLParam(r *http.Request, name string) string

URLParam returns the named URL parameter from the request's RouteContext. It is intended for use in stdlib handlers mounted via App.Mount; normal Credo handlers should use Request.RouteParams instead. Returns empty string if the parameter doesn't exist or no RouteContext is set.

func Walk

func Walk(r Routes, fn WalkFunc) error

Walk iterates over all registered routes, calling fn for each one.

func WalkRoutes

func WalkRoutes(r Routes, fn WalkRoutesFunc) error

WalkRoutes iterates over all registered routes with full RouteInfo, calling fn for each one.

Types

type App

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

App is the main Credo application. It holds the router, middleware stack, context pool, error handler, and route registry.

func New

func New(opts ...Option) (*App, error)

New creates a new App with the given options. Returns an error if the configuration contains invalid values (negative timeouts, invalid port).

Usage:

// Zero-config (all defaults):
app, err := credo.New()

// With listen address:
app, err := credo.New(credo.WithAddr("127.0.0.1", 8080))

// With explicit RawConfig (server settings read from "server" key):
app, err := credo.New(credo.WithRawConfig(rawCfg))

func (*App) AddLivenessCheck

func (app *App) AddLivenessCheck(name string, checker HealthChecker)

AddLivenessCheck registers a named liveness check. Panics if App.UseHealth has not been called first or if checker is nil.

func (*App) AddReadinessCheck

func (app *App) AddReadinessCheck(name string, checker HealthChecker)

AddReadinessCheck registers a named readiness check. Panics if App.UseHealth has not been called first or if checker is nil.

func (*App) Addr

func (app *App) Addr() net.Addr

Addr returns the actual network address the server is listening on. This is particularly useful when the server was started with port 0, as the OS assigns an ephemeral port. Returns nil before Run or after the server stops.

func (*App) Context

func (app *App) Context() context.Context

Context returns the app-level context. This context is created when Run is called and cancelled at the beginning of Shutdown. Background services (cron jobs, pub/sub subscribers, gRPC servers) should select on ctx.Done() to detect graceful shutdown.

WARNING: Context must be called after Run. Before Run returns (or from another goroutine before Run is invoked), Context returns context.Background(), which is never cancelled. Goroutines that capture this pre-run value will not receive the shutdown signal.

func (*App) DELETE

func (app *App) DELETE(pattern string, h Handler) *Route

DELETE registers a DELETE route. Panics under the same conditions as App.GET.

func (*App) File

func (app *App) File(urlPath string, fsys fs.FS, name string, cfgs ...StaticConfig) *Route

File registers a single GET route that serves one named file from fsys. Returns *Route for standard fluent chaining (Name, SetMeta, Middleware).

Only the Download and CacheControl fields of StaticConfig are supported. Setting Browse, SPA, or a non-empty Index panics at registration time.

Panics if called after compile.

func (*App) GET

func (app *App) GET(pattern string, h Handler) *Route

GET registers a GET route. A matching HEAD handler is registered automatically; subsequent calls to Route.Middleware and Route.SetMeta on the returned route apply to HEAD as well, so HEAD requests can never silently bypass auth, rate limiting, or meta-driven middleware.

Panics if h is nil, the pattern is invalid or already registered, or if called after compile (route registration is developer configuration — see the package-level "Panics and Errors" section).

func (*App) GetRoute

func (app *App) GetRoute(name string) *Route

GetRoute returns a named route by name.

func (*App) GlobalMiddleware

func (app *App) GlobalMiddleware(middlewares ...Middleware)

GlobalMiddleware appends middleware that runs on every request, including 404 and 405 responses. Must be called before the server starts; panics if called after compile.

func (*App) Group

func (app *App) Group(prefix string) *Group

Group creates a new route group with the given prefix.

func (*App) HEAD

func (app *App) HEAD(pattern string, h Handler) *Route

HEAD registers an explicit HEAD route (overrides auto-generated one). Panics under the same conditions as App.GET.

func (*App) Host

func (app *App) Host(pattern string) *Group

Host creates a route group scoped to the given host pattern. Pattern supports exact labels ("api.example.com"), named parameters ("{tenant}.example.com", "{org:[a-z]+}.platform.io"), and a leftmost anonymous wildcard label ("*.example.com"). Routes registered on the returned Group only match requests whose Host header matches the pattern. Unmatched hosts fall back to the default mux. Returns *Group for API consistency with App.Group.

Host panics if the pattern is a duplicate, overlaps an existing pattern with identical match semantics, contains an invalid wildcard, an invalid regex constraint, or a port. Registering a route on the returned Group panics if a route parameter name collides with a host parameter name. Must be called before the server starts; panics if called after compile.

func (*App) IsDebug

func (app *App) IsDebug() bool

IsDebug reports whether the application is running in debug mode. Debug mode enables development-time warnings (e.g., bind targets that do not implement Validatable). Activated via WithDebug or the server.debug config key.

func (*App) IsRunning

func (app *App) IsRunning() bool

IsRunning reports whether the server is in the running state.

func (*App) Logger

func (app *App) Logger() *slog.Logger

Logger returns the application-level logger used by framework internals. Worker and other integration packages use this accessor to derive framework-scoped loggers without exposing raw logger registration in DI.

func (*App) Mount

func (app *App) Mount(pattern string, handler http.Handler)

Mount attaches another http.Handler as a sub-router under the given pattern. The sub-router receives the remainder of the URL path.

Method scope: the mounted handler is registered for all standard HTTP methods except CONNECT and TRACE, which are excluded deliberately (CONNECT is a proxy mechanism; TRACE enables cross-site tracing). Requests using them receive 405 Method Not Allowed.

Middleware scope: mounted handlers receive only built-in and global middleware. Group-level and route-level middleware do not apply because mounted handlers are plain http.Handler instances dispatched outside the per-route compiled chain. If the mounted sub-application requires authentication or other protections, it must enforce them internally or the protections must be registered as global middleware.

The parent's RouteContext (which may contain internal params like _mount) is stripped before calling the child handler, so the child dispatch creates its own fresh RouteContext. This prevents internal routing state from leaking across mount boundaries.

Must be called before the server starts; panics if called after compile.

func (*App) Mux

func (app *App) Mux() Routes

Mux returns a route registry view for route introspection (Walk, Routes). The returned view includes routes from the default mux and all host-scoped muxes.

func (*App) NewInfra

func (app *App) NewInfra(name string) Infra

NewInfra creates a scoped Infra with the given name. The Logger is tagged with "service"=name and falls back to the framework default logger when the application has none configured.

Use NewInfra for components that live outside the DI container (middleware factories, startup helpers, workers created manually). For DI-managed services, Infra is injected automatically.

func (*App) OPTIONS

func (app *App) OPTIONS(pattern string, h Handler) *Route

OPTIONS registers an OPTIONS route. Panics under the same conditions as App.GET.

func (*App) OnShutdown

func (app *App) OnShutdown(fn func(ctx context.Context) error)

OnShutdown registers a function to be called during graceful shutdown. Hooks are called in LIFO order (last registered, first called). The ctx passed to each hook carries the shutdown deadline from Shutdown(ctx). Must be called before Run; panics if called after compile.

func (*App) OnStart

func (app *App) OnStart(fn func(ctx context.Context) error)

OnStart registers a function to be called during startup, after the port is bound but before the server starts accepting connections. Hooks are called in FIFO order (first registered, first called). The ctx passed to each hook is the app context (created at Run time). If any hook returns an error, the server does not start and Run returns the error. Typical uses are cache warm-up and database migrations — the store/sqldb migration wrapper plugs in directly: app.OnStart(db.Migrate). Must be called before Run; panics if called after compile.

func (*App) PATCH

func (app *App) PATCH(pattern string, h Handler) *Route

PATCH registers a PATCH route. Panics under the same conditions as App.GET.

func (*App) POST

func (app *App) POST(pattern string, h Handler) *Route

POST registers a POST route. Panics under the same conditions as App.GET.

func (*App) PUT

func (app *App) PUT(pattern string, h Handler) *Route

PUT registers a PUT route. Panics under the same conditions as App.GET.

func (*App) RemoveMeta

func (app *App) RemoveMeta(key string)

RemoveMeta removes a root-level metadata key. Must be called before the server starts; panics if called after compile.

func (*App) Routes

func (app *App) Routes() []RouteInfo

Routes returns introspection data for all registered routes across the default mux and all host-scoped muxes.

func (*App) Run

func (app *App) Run() error

Run starts the HTTP server using the address from server config. Returns nil if the server was shut down gracefully via Shutdown. Returns an error if the server is already running or fails to start.

func (*App) RunTLS

func (app *App) RunTLS(certFile, keyFile string) error

RunTLS starts the HTTPS server using the address from server config. Returns nil if the server was shut down gracefully via Shutdown. Returns an error if the server is already running or fails to start.

func (*App) ServeHTTP

func (app *App) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP implements http.Handler. It compiles the handler chain on first call using sync.Once for thread safety.

func (*App) SetErrorRenderer

func (app *App) SetErrorRenderer(r ErrorRenderer)

SetErrorRenderer sets the renderer that formats error responses. The framework handles error classification, logging, HEAD handling, and committed-response guards internally; the renderer receives an ErrorInfo containing the original error, the i18n message key, and the classified ProblemDetails. Passing nil restores the default RFC 7807 JSON renderer.

Must be called before the server starts; panics if called after compile.

func (*App) SetMeta

func (app *App) SetMeta(key string, val any)

SetMeta sets a root-level metadata key-value pair. Must be called before the server starts; panics if called after compile.

func (*App) Shutdown

func (app *App) Shutdown(ctx context.Context) error

Shutdown gracefully shuts down the server. It drains in-flight requests, then calls registered OnShutdown hooks in LIFO order. Returns an error if the server is not in the running state, or if any shutdown step fails (all errors are collected via errors.Join).

func (*App) State

func (app *App) State() string

State returns the current lifecycle state as a string.

func (*App) Static

func (app *App) Static(prefix string, fsys fs.FS, cfgs ...StaticConfig) *StaticRoute

Static registers routes that serve files from fsys under the given URL prefix. Returns a *StaticRoute for fluent configuration (Name, SetMeta, Middleware).

Internally registers two routes: a catch-all for file paths and an exact match for the prefix itself (serves the index file). Both share the same handler and receive fluent configuration uniformly via StaticRoute.

Panics if prefix contains { or } (route parameters in a static prefix are not meaningful) or if called after compile. The cache presets (StaticCacheMaxAge, StaticCacheImmutableAssets) panic at their own call site on invalid durations.

Production recommendation: use DirFS (or os.Root.FS()) for symlink-safe disk serving. os.DirFS does not prevent symlink-based path traversal.

func (*App) StatusHandler

func (app *App) StatusHandler(code int, h Handler)

StatusHandler sets a custom handler for the given HTTP status code at the root level. Must be called before the server starts; panics if called after compile.

func (*App) UseHealth

func (app *App) UseHealth(cfgs ...HealthConfig)

UseHealth initializes health check endpoints on the application. With no arguments, it registers both /health (liveness) and /ready (readiness).

UseHealth performs no I/O — it only registers in-process state — so misuse panics like every other registration API (contrast App.UseI18n, which reads locale files and therefore returns an error). Panics if called more than once, if called after compile, or if cfg.Group belongs to a different App.

func (*App) UseI18n

func (app *App) UseI18n(cfgs ...I18nConfig) error

UseI18n initializes i18n for the application. It loads locale files, stores the bundle, and adds a global middleware for locale detection.

Behavior:

  • No args or zero-value cfg: reads RawConfig "i18n" key; if absent, uses defaults (dir="locales/", default="en").
  • Dir doesn't exist or is empty: returns nil (i18n inactive, no middleware).
  • Malformed files: returns error.
  • Valid files: loads bundle, adds locale detection middleware.

Unlike registration-only setup APIs such as App.UseHealth, UseI18n reads locale files from disk or an fs.FS — an external operation that can fail for reasons other than a programming mistake — so failures are returned as errors rather than panicking. It still panics if called after compile, like all configuration APIs.

type Context

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

Context is the request-scoped struct that holds the Request, Response, matched Route, logger, and a key-value store.

Context is pooled via sync.Pool for zero-allocation request handling. Access fields only through methods (not direct field access) to ensure pool safety. Because it is pooled, Context deliberately does not implement context.Context; call Context.Context to obtain the request's context.Context for context-taking APIs.

func NewContext

func NewContext(w http.ResponseWriter, r *http.Request) *Context

NewContext creates a new Context wrapping the given ResponseWriter and Request. This is primarily useful for testing error handlers and middleware.

func (*Context) AddLogAttrs

func (c *Context) AddLogAttrs(args ...any)

AddLogAttrs adds attributes to the request-scoped logger for the remainder of this request. The new logger is derived from Context.Logger, so enrichment added earlier — such as the request_id attribute — is preserved by construction. args are key-value pairs as accepted by slog.Logger.With:

ctx.AddLogAttrs("tenant_id", tenantID)

Prefer this over Context.SetLogger when the goal is to add attributes rather than replace the logger. Calling it with no arguments is a no-op.

func (*Context) Context

func (c *Context) Context() context.Context

Context returns the underlying request's context.Context. Use it for APIs that take a context.Context — database queries, downstream requests, or github.com/credo-go/credo/auth.GetUser:

user, ok := auth.GetUser[*User](ctx.Context())

The returned context is canceled when the request completes. For background work that must outlive the request, detach it with context.WithoutCancel:

bg := context.WithoutCancel(ctx.Context())
go process(bg)

Context deliberately does NOT implement context.Context itself: it is pooled (sync.Pool) and reused across requests, so retaining a *Context as a long-lived context.Context would observe a later request's state. Context hands back the real, non-pooled request context instead.

func (*Context) Get

func (c *Context) Get(key string) any

Get retrieves a value from the request-scoped store.

func (*Context) HasRequestLogger

func (c *Context) HasRequestLogger() bool

HasRequestLogger reports whether a request-scoped logger has been set for this request — by the built-in request ID tier, middleware.RequestID, Context.SetLogger, or Context.AddLogAttrs. It does not inspect the logger's attributes.

The framework's log emitters (access log, panic recovery) use it as a convention-based signal: under the derivation contract documented on Context.SetLogger, a request-scoped logger is assumed to already carry request_id, so the emitters skip adding the attribute explicitly.

func (*Context) HasRoute

func (c *Context) HasRoute() bool

HasRoute reports whether a route matched for this request. Returns false inside custom 404/405 status handlers where no route exists. Use this to guard Context.Route calls in middleware that may run on unmatched-request paths:

if ctx.HasRoute() {
    val, _ := ctx.Route().LookupMeta("permission")
}

func (*Context) IsRewriting

func (c *Context) IsRewriting() bool

IsRewriting reports whether this context has a pending internal rewrite. This can be useful in middleware to avoid side-effects (e.g., response writing) when the handler signaled a re-dispatch.

func (*Context) Locale

func (c *Context) Locale() string

Locale returns the detected locale string for this request (e.g., "en", "tr"). Returns an empty string if i18n is not configured.

func (*Context) Logger

func (c *Context) Logger() *slog.Logger

Logger returns the request-scoped logger. Falls back through the chain: request logger → app logger → nop logger.

func (*Context) OriginalPath

func (c *Context) OriginalPath() string

OriginalPath returns the request path as received from the client, before any rewriting (middleware.Rewrite or ctx.Rewrite). Useful for access logging, analytics, and debugging.

func (*Context) Request

func (c *Context) Request() *Request

Request returns the Request for this context.

func (*Context) RequestID

func (c *Context) RequestID() string

RequestID returns the current request ID. It returns an empty string when request ID middleware is not active.

func (*Context) Response

func (c *Context) Response() *Response

Response returns the Response for this context.

func (*Context) Rewrite

func (c *Context) Rewrite(path string) error

Rewrite triggers an internal re-dispatch to the given path. The client is unaware of the rewrite (no HTTP redirect is sent). The original request path is preserved in OriginalPath(). The matched host scope does not change.

Rewrite must be the last call in a handler — the return value must be returned directly:

return ctx.Rewrite("/new-path")

A maximum of 10 rewrites per request is enforced to prevent loops. If exceeded, an error is returned and the request fails with 500.

func (*Context) Route

func (c *Context) Route() *Route

Route returns the matched Route (for accessing Meta, Name, BuildURI). Returns nil when no route matched (e.g., inside custom 404/405 handlers). Use Context.HasRoute to guard against nil before calling Route methods.

func (*Context) Set

func (c *Context) Set(key string, val any)

Set stores a key-value pair in the request-scoped store.

func (*Context) SetLogger

func (c *Context) SetLogger(l *slog.Logger)

SetLogger replaces the request-scoped logger for the remainder of this request. It is the wholesale-replacement API: reach for it when the logger itself must change (different handler, level, or destination). To add request-bound attributes, prefer Context.AddLogAttrs.

When replacing, derive the new logger from Context.Logger so existing enrichment is preserved:

ctx.SetLogger(ctx.Logger().With("tenant_id", tenantID))

A logger built from scratch silently drops attributes added earlier — including the request_id added by the request ID tier — from all subsequent log output (handler logs and the access log). The framework cannot detect this; if you must start from a fresh logger, re-attach the ID via Context.RequestID.

The logger is cleared automatically when the request completes.

func (*Context) T

func (c *Context) T(key string, data ...map[string]any) string

T translates a message key using the detected locale. If i18n is not configured or the key is not found, the key itself is returned. Optional data map provides template variables for the message.

T always renders the message's Other plural form; for count-based plural selection use Context.TPlural.

func (*Context) TPlural

func (c *Context) TPlural(key string, count any, data ...map[string]any) string

TPlural translates a message key using the detected locale, rendering the CLDR cardinal plural form selected for count. count may be any integer kind, an integral float, or a decimal string ("1.5") when visible fraction digits matter. The count value is exposed to the template as {{.count}}.

// messages.json: "items": {"one": "{{.count}} item", "other": "{{.count}} items"}
ctx.TPlural("items", 1) // "1 item"
ctx.TPlural("items", 5) // "5 items"

If i18n is not configured or the key is not found, the key itself is returned. When count cannot be interpreted as a number, the Other form is rendered.

type ErrorInfo

type ErrorInfo struct {
	// Err is the original error returned by the handler.
	// Use [errors.As] / [errors.Is] for type-specific behavior
	// (e.g., Sentry reporting, extracting metadata for custom headers).
	Err error

	// MessageKey is the i18n message key used to resolve [ProblemDetails.Title].
	// This is the raw key before resolution (e.g., "http.not_found",
	// "user.email_exists"). Useful for client-side i18n, telemetry
	// grouping, or custom error code mapping.
	MessageKey string

	// Problem is the framework-classified RFC 7807 Problem Details.
	// The renderer may use it as-is, modify it, or ignore it entirely
	// and write a custom response format.
	Problem *ProblemDetails
}

ErrorInfo carries the original error and the framework-classified ProblemDetails to the ErrorRenderer.

type ErrorRenderer

type ErrorRenderer func(ctx *Context, info ErrorInfo)

ErrorRenderer formats an error response given a classified ErrorInfo. The framework handles error classification, logging, and committed-response guards internally. ErrorRenderer is called for all HTTP methods including HEAD, allowing it to set response headers (e.g., Retry-After, WWW-Authenticate). For HEAD requests where the renderer does not commit the response, the framework sends a status-only response (no body).

If ErrorRenderer does not write a response (Response.Committed remains false), the framework sends a status-only response for HEAD, or falls back to the default RFC 7807 JSON renderer for other methods.

Register a custom renderer with App.SetErrorRenderer.

type Group

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

Group is the user-facing route group for configuring routes with a shared prefix, middleware, and metadata. It is also the node of the group parent chain: route metadata (LookupMeta) and group middleware are both resolved by walking this chain — metadata dynamically at request time, middleware once at compile time.

func (*Group) DELETE

func (g *Group) DELETE(pattern string, h Handler) *Route

DELETE registers a DELETE route in this group. Panics under the same conditions as App.GET.

func (*Group) File

func (g *Group) File(urlPath string, fsys fs.FS, name string, cfgs ...StaticConfig) *Route

File registers a single GET route that serves one named file from fsys within this group. See App.File for full documentation.

func (*Group) GET

func (g *Group) GET(pattern string, h Handler) *Route

GET registers a GET route in this group. A matching HEAD handler is registered automatically; subsequent calls to Route.Middleware and Route.SetMeta on the returned route apply to HEAD as well, so HEAD requests can never silently bypass auth, rate limiting, or meta-driven middleware. Panics under the same conditions as App.GET.

func (*Group) Group

func (g *Group) Group(prefix string) *Group

Group creates a nested sub-group with the given prefix. The sub-group inherits this group's metadata and middleware through the parent chain.

func (*Group) HEAD

func (g *Group) HEAD(pattern string, h Handler) *Route

HEAD registers a HEAD route in this group. Panics under the same conditions as App.GET.

func (*Group) Middleware

func (g *Group) Middleware(middlewares ...Middleware) *Group

Middleware appends group-level middlewares. They apply to every route of this group and its sub-groups, regardless of whether the route was registered before or after this call: per-route chains are assembled from the group parent chain when the app compiles (first request or Run), mirroring how route metadata is resolved via Route.LookupMeta. Must be called before the server starts; panics if called after compile. Returns *Group for chaining.

func (*Group) OPTIONS

func (g *Group) OPTIONS(pattern string, h Handler) *Route

OPTIONS registers an OPTIONS route in this group. Panics under the same conditions as App.GET.

func (*Group) PATCH

func (g *Group) PATCH(pattern string, h Handler) *Route

PATCH registers a PATCH route in this group. Panics under the same conditions as App.GET.

func (*Group) POST

func (g *Group) POST(pattern string, h Handler) *Route

POST registers a POST route in this group. Panics under the same conditions as App.GET.

func (*Group) PUT

func (g *Group) PUT(pattern string, h Handler) *Route

PUT registers a PUT route in this group. Panics under the same conditions as App.GET.

func (*Group) RemoveMeta

func (g *Group) RemoveMeta(key string)

RemoveMeta removes a metadata key from this group. Must be called before the server starts; panics if called after compile.

func (*Group) SetMeta

func (g *Group) SetMeta(key string, val any)

SetMeta sets a metadata key-value pair for this group. All routes within the group inherit this metadata. Must be called before the server starts; panics if called after compile.

func (*Group) Static

func (g *Group) Static(prefix string, fsys fs.FS, cfgs ...StaticConfig) *StaticRoute

Static registers routes that serve files from fsys under the given URL prefix within this group. See App.Static for full documentation.

type HTTPError

type HTTPError struct {
	// Code is the HTTP status code.
	Code int `json:"code"`

	// MessageKey is the i18n message key or literal fallback message.
	MessageKey string `json:"message_key"`

	// Internal is the underlying error (not exposed to the client).
	Internal error `json:"-"`
}

HTTPError represents an HTTP error with a status code and a message key. The MessageKey field serves as both the i18n translation key and the fallback message when no translation is found.

Resolution order for MessageKey (applied in the error handling pipeline):

  1. i18n bundle lookup — if a translation exists for MessageKey, use it
  2. builtInMessages lookup — if MessageKey matches a built-in key, use it
  3. MessageKey itself — used as-is (works for literal messages)

func NewHTTPError

func NewHTTPError(code int, messageKey ...string) *HTTPError

NewHTTPError creates a new HTTPError with the given status code and optional message key. If no message key is provided, the corresponding MsgKey constant is used (falling back to http.StatusText for unknown codes).

func (*HTTPError) Error

func (e *HTTPError) Error() string

Error implements the error interface.

func (*HTTPError) HTTPStatus

func (e *HTTPError) HTTPStatus() int

HTTPStatus returns the HTTP status code carried by the error.

func (*HTTPError) Unwrap

func (e *HTTPError) Unwrap() error

Unwrap returns the internal error, supporting errors.Is/As.

func (*HTTPError) WithInternal

func (e *HTTPError) WithInternal(err error) *HTTPError

WithInternal returns a copy of the error with the internal error set.

type Handler

type Handler func(ctx *Context) error

Handler is the Credo handler signature. Handlers return an error for centralized error handling via the App's error handling pipeline.

type HealthCheckFunc

type HealthCheckFunc func(ctx context.Context) error

HealthCheckFunc adapts a plain function to HealthChecker.

func (HealthCheckFunc) Check

func (f HealthCheckFunc) Check(ctx context.Context) error

Check implements HealthChecker.

type HealthChecker

type HealthChecker interface {
	Check(ctx context.Context) error // nil = healthy
}

HealthChecker checks the health of a component.

type HealthConfig

type HealthConfig struct {
	// Enabled controls whether health endpoints are registered.
	// nil (default) = true.
	Enabled *bool

	// Liveness controls whether the liveness endpoint is registered.
	// nil (default) = true.
	Liveness *bool

	// Readiness controls whether the readiness endpoint is registered.
	// nil (default) = true.
	Readiness *bool

	// LivenessPath is the path for the liveness endpoint. Default: "/health".
	LivenessPath string

	// ReadinessPath is the path for the readiness endpoint. Default: "/ready".
	ReadinessPath string

	// CheckTimeout is the per-check timeout. Default: 5s.
	CheckTimeout time.Duration

	// ExposeErrors includes check error strings in the readiness response
	// body. Default false: failing checks report only "down" and the error
	// is written to the application log instead — error strings often
	// carry internal details (hostnames, connection targets) that should
	// not reach unauthenticated probe endpoints.
	ExposeErrors bool

	// Group registers health routes on a specific route group instead of the
	// app root. Routes inherit the group's prefix and middleware chain.
	// nil (default) = routes are registered on the app root.
	Group *Group
}

HealthConfig configures the health check endpoints.

type I18nConfig

type I18nConfig struct {
	// Dir is the filesystem path to the locale directory (e.g., "locales/").
	// Mutually exclusive with DirFS.
	Dir string

	// DirFS is an embed.FS or any fs.FS providing locale files.
	// Mutually exclusive with Dir.
	DirFS fs.FS

	// Default is the default language tag string (e.g., "en").
	// Falls back to "en" if empty.
	Default string

	// Detect is a function that extracts the preferred language from an HTTP request.
	// Defaults to reading the Accept-Language header if nil.
	Detect func(r *http.Request) string
}

I18nConfig configures internationalization for the application.

type Infra

type Infra struct {

	// Logger is a structured logger, scoped to the service.
	Logger *slog.Logger
	// contains filtered or unexported fields
}

Infra carries framework infrastructure to services. The container produces it automatically when detected as a constructor parameter (Model 1).

Logger is scoped per service: each service receives a logger with a "service" attribute set to the service type name.

When no logger is configured via WithLogger, Infra falls back to a default stderr logger. Future infrastructure (metrics, tracing) will be added as new fields by the observability release.

For testing, construct Infra directly:

infra := credo.Infra{Logger: slog.New(slog.NewTextHandler(os.Stderr, nil))}

type Middleware

type Middleware func(next Handler) Handler

Middleware is the single middleware type used throughout Credo. All middleware — global, group, and route level — uses this signature.

func WrapStdMiddleware

func WrapStdMiddleware(m StdMiddleware) Middleware

WrapStdMiddleware converts stdlib middleware (func(http.Handler) http.Handler) into a Credo Middleware (func(Handler) Handler). This allows using any existing Go community middleware with Credo's unified middleware stack.

app.GlobalMiddleware(credo.WrapStdMiddleware(corsMiddleware))

type Option

type Option func(*appOptions)

Option configures the App during construction.

func WithAddr

func WithAddr(host string, port int) Option

WithAddr sets the listen address directly (for testing or programmatic use).

func WithDebug

func WithDebug() Option

WithDebug enables development-mode warnings. When active, the framework logs warnings for common mistakes such as binding a struct that does not implement validation.Validatable. Can also be enabled via the server.debug config key.

func WithLogger

func WithLogger(l *slog.Logger) Option

WithLogger sets the application-level logger. Each service receives a scoped copy with a "service" attribute. If not set, a nop logger is used.

func WithMaxBodyBytes

func WithMaxBodyBytes(n int64) Option

WithMaxBodyBytes sets the maximum number of bytes read from any request body. Requests whose body exceeds the limit receive 413 Request Entity Too Large. A negative value disables the limit; zero (the default) applies a 4 MiB cap.

func WithRawConfig

func WithRawConfig(rc RawConfig) Option

WithRawConfig sets the RawConfig for the application. The framework reads server settings from the "server" key internally. When not provided, credo.New automatically loads configuration via config.Load.

func WithRedirectTrailingSlash

func WithRedirectTrailingSlash(enabled bool) Option

WithRedirectTrailingSlash controls whether the router automatically redirects requests whose trailing slash variant matches a registered route. GET/HEAD requests receive 301; other methods receive 308 (preserving the method). Defaults to true when not set.

func WithTrustedProxies

func WithTrustedProxies(cidrs ...string) Option

WithTrustedProxies configures the CIDR ranges from which forwarded headers such as X-Forwarded-For, X-Forwarded-Proto, and X-Real-IP are trusted. Requests whose immediate peer is outside this list ignore forwarded headers.

Pass no entries (the default) to disable proxy-header trust entirely. Invalid CIDR entries cause New to return an error.

func WithoutAccessLog

func WithoutAccessLog() Option

WithoutAccessLog disables the built-in access logger. By default, every request is logged with method, path, status, bytes, duration, remote_addr (from Request.RealIP), and user_agent attributes.

Disable this if you use [middleware.AccessLog] with custom configuration (e.g., a Skipper function). Using both the built-in and middleware loggers produces duplicate log entries.

func WithoutRecover

func WithoutRecover() Option

WithoutRecover disables the built-in panic recovery that wraps the entire handler chain. By default, Credo recovers from panics in all middleware and handlers, logs the stack trace, and returns 500 Internal Server Error.

Disable this if you provide your own recovery mechanism or need panics to propagate (e.g., in tests).

func WithoutRequestID

func WithoutRequestID() Option

WithoutRequestID disables the built-in request ID middleware. By default, every request gets a unique ID (set on context and X-Request-Id header), and the request-scoped logger is enriched with the request_id attribute.

Disable this if you use [middleware.RequestID] with custom configuration (e.g., different header name, custom generator). Note that the built-in access logger will still work but request_id will not appear in logs unless the custom middleware also enriches ctx.Logger().

type ProblemDetails

type ProblemDetails struct {
	// Type is a URI reference that identifies the problem type.
	// Defaults to "about:blank" per RFC 7807.
	Type string `json:"type"`

	// Title is a short, human-readable summary of the problem type.
	Title string `json:"title"`

	// Status is the HTTP status code.
	Status int `json:"status"`

	// Detail is a human-readable explanation specific to this occurrence.
	Detail string `json:"detail,omitempty"`

	// Instance is a URI reference that identifies the specific occurrence.
	Instance string `json:"instance,omitempty"`

	// Errors holds field-level validation errors (if any).
	Errors []validation.ValidationError `json:"errors,omitempty"`
}

ProblemDetails represents an RFC 7807 Problem Details response.

func NewProblemDetails

func NewProblemDetails(status int, title string) *ProblemDetails

NewProblemDetails creates a new ProblemDetails with the given status and title. Type defaults to "about:blank" per RFC 7807.

type RawConfig

type RawConfig = config.RawConfig

RawConfig is an alias for config.RawConfig. The interface is defined in the config package to avoid circular imports between the root package and the config package. This alias ensures that credo.RawConfig continues to work seamlessly throughout the framework.

RawConfig provides low-level access to the merged configuration store. This is a bootstrap mechanism — application code should use typed config structs injected via DI instead of calling RawConfig directly.

Unmarshal decodes both struct sections and primitive values:

var port int
store.Unmarshal("server.port", &port)

var dbCfg DatabaseConfig
store.Unmarshal("databases.default", &dbCfg)

type Request

type Request struct {
	*http.Request
	// contains filtered or unexported fields
}

Request wraps *http.Request and provides request-side helpers: route parameters, query parameter shortcuts, and body/query binding.

func NewRequest

func NewRequest(r *http.Request) *Request

NewRequest creates a new Request wrapping the given *http.Request.

func (*Request) BindBody

func (r *Request) BindBody(target any) error

BindBody decodes the request body into target based on the Content-Type header. Supported content types:

  • application/json (default when Content-Type is absent)
  • application/xml, text/xml
  • application/x-www-form-urlencoded (uses "form" struct tags)
  • multipart/form-data (uses "form" struct tags, including file fields)

If target implements validation.Validatable, Validate() is called automatically after successful decoding ("parse, don't validate").

Returns 400 Bad Request for empty body or decode errors, 415 Unsupported Media Type for unrecognized content types.

func (*Request) BindQuery

func (r *Request) BindQuery(target any) error

BindQuery decodes URL query parameters into target using `query:"name"` struct tags. If target implements validation.Validatable, Validate() is called automatically after successful decoding ("parse, don't validate").

In debug mode, a warning is logged when the target does not implement Validatable.

func (*Request) PathValue

func (r *Request) PathValue(name string) string

PathValue returns the route parameter for name, falling back to the embedded request's http.Request.PathValue. Prefer Request.RouteParam in new code.

This shadow exists for stdlib muscle memory: Credo's dispatcher does not populate the embedded *http.Request's path values (doing so would cost an allocation per request for data Request.RouteParam already serves), so without it ctx.Request().PathValue("id") would silently return "". The raw embedded request — as seen by stdlib handlers via App.Mount or middleware via WrapStdMiddleware — still carries no path values.

func (*Request) QueryParam

func (r *Request) QueryParam(name string) string

QueryParam returns a query string parameter value by name. Returns "" if not present.

func (*Request) RealIP

func (r *Request) RealIP() string

RealIP returns the address of the original client.

When the immediate peer RemoteAddr is trusted, RealIP walks the proxy chain — the RFC 7239 Forwarded header's for= parameters first, then X-Forwarded-For — from right to left, skipping trusted proxy hops and returning the first untrusted address. If neither yields a usable value, X-Real-IP is used. If the peer is untrusted, all forwarded headers are ignored.

The returned value is an IP address only for parseable addresses. If RemoteAddr itself is unparseable, RealIP falls back to RemoteAddr verbatim.

func (*Request) RouteParam

func (r *Request) RouteParam(name string) string

RouteParam returns the URL parameter value for name — a path parameter such as {id}, or, for host-scoped routes, a host parameter. It returns "" when the parameter is not present.

RouteParam is the preferred accessor for single values; it never allocates:

id := ctx.Request().RouteParam("id")

Unlike the map returned by Request.RouteParams, the returned string is safe to retain after the request completes.

func (*Request) RouteParams

func (r *Request) RouteParams() map[string]string

RouteParams returns all URL parameter key-value pairs. For host-scoped routes, host params and path params share the same namespace.

The map is a read-only view, materialized lazily on first call: writes to it are not seen by Request.RouteParam. It is owned by the framework and recycled after the request completes — do not retain it or read it from another goroutine after the handler returns. For single values, prefer Request.RouteParam.

func (*Request) Scheme

func (r *Request) Scheme() string

Scheme reports the scheme the original client used: "http" or "https".

If the request arrived over TLS directly, Scheme returns "https". Otherwise, forwarded scheme headers are considered only when the immediate peer RemoteAddr is configured as a trusted proxy on the App. Untrusted peers cannot influence the result.

Only "http" and "https" are returned. Invalid forwarded header values fall back to the underlying transport.

type Response

type Response struct {
	http.ResponseWriter
	// contains filtered or unexported fields
}

Response wraps http.ResponseWriter to track the response status code, byte count, and whether headers have been committed.

The embedded ResponseWriter may be swapped by middleware that wraps the writer (e.g., compression); the tracking state is framework-owned and exposed read-only via Response.Status, Response.Size, and Response.Committed.

func NewResponse

func NewResponse(w http.ResponseWriter) *Response

NewResponse creates a new Response wrapping the given http.ResponseWriter.

func (*Response) Blob

func (r *Response) Blob(code int, contentType string, b []byte) error

Blob sends a binary response with the given content type.

func (*Response) Committed

func (r *Response) Committed() bool

Committed reports whether the response header has been written. Once committed, the status code and headers can no longer change.

func (*Response) Flush

func (r *Response) Flush()

Flush sends any buffered data to the client.

func (*Response) HTML

func (r *Response) HTML(code int, html string) error

HTML sends an HTML response with the given status code.

func (*Response) Hijack

func (r *Response) Hijack() (net.Conn, *bufio.ReadWriter, error)

Hijack implements the http.Hijacker interface.

func (*Response) JSON

func (r *Response) JSON(code int, v any) error

JSON sends a JSON response with the given status code.

func (*Response) NoContent

func (r *Response) NoContent(code int) error

NoContent sends a response with no body.

func (*Response) Redirect

func (r *Response) Redirect(code int, url string) error

Redirect sends an HTTP redirect response.

func (*Response) Reset

func (r *Response) Reset(w http.ResponseWriter)

Reset resets the response for reuse from the pool.

func (*Response) SetCookie

func (r *Response) SetCookie(cookie *http.Cookie)

SetCookie adds a Set-Cookie header to the response.

func (*Response) Size

func (r *Response) Size() int64

Size returns the number of bytes written to the response body.

func (*Response) Status

func (r *Response) Status() int

Status returns the HTTP status code written, or 0 when the response has not been committed yet.

func (*Response) Stream

func (r *Response) Stream(code int, contentType string, rd io.Reader) error

Stream sends a streaming response from the given reader.

func (*Response) String

func (r *Response) String() string

String implements fmt.Stringer for debugging.

func (*Response) Text

func (r *Response) Text(code int, s string) error

Text sends a plain text response with the given status code. Named Text (not String) to avoid conflict with the fmt.Stringer interface.

func (*Response) Unwrap

func (r *Response) Unwrap() http.ResponseWriter

Unwrap returns the underlying http.ResponseWriter.

func (*Response) Write

func (r *Response) Write(b []byte) (int, error)

Write writes the data to the connection as part of an HTTP reply. If WriteHeader has not been called, it calls WriteHeader(200).

func (*Response) WriteHeader

func (r *Response) WriteHeader(code int)

WriteHeader sends an HTTP response header with the given status code. It can only be called once per response.

func (*Response) XML

func (r *Response) XML(code int, v any) error

XML sends an XML response with the given status code.

type Route

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

Route represents a registered route with its handler, metadata, and fluent configuration methods.

func (*Route) BuildURI

func (r *Route) BuildURI(params ...string) (string, error)

BuildURI generates a URI from the route pattern by replacing named parameters with the provided values in order. It returns an error when a parameter is missing, when too many values are provided, or when the route pattern is malformed. Uses brace-depth-aware parsing to correctly handle regex quantifiers like {id:[0-9]{2,4}}.

uri, err := route.BuildURI("42") // "/users/42"

func (*Route) BuildURL

func (r *Route) BuildURL(params ...string) (string, error)

BuildURL generates a full URL by combining the route's host pattern and path pattern, replacing parameters with the provided values in order. Host pattern parameters are consumed first, then path parameters. It returns an error when a parameter is missing, when too many values are provided, or when either pattern is malformed. Wildcard host patterns cannot generate concrete URLs and return an error.

For host-scoped routes:

// Host: {tenant}.myapp.com, Path: /users/{id}
url, err := route.BuildURL("acme", "42") // "acme.myapp.com/users/42"

For default (non-host-scoped) routes, BuildURL is equivalent to Route.BuildURI:

url, err := route.BuildURL("42") // "/users/42"

func (*Route) GetHost

func (r *Route) GetHost() string

GetHost returns the host pattern for host-scoped routes. Returns an empty string for routes on the default mux.

func (*Route) GetMethod

func (r *Route) GetMethod() string

GetMethod returns the HTTP method.

func (*Route) GetName

func (r *Route) GetName() string

GetName returns the route name.

func (*Route) GetPattern

func (r *Route) GetPattern() string

GetPattern returns the URL pattern.

func (*Route) LookupMeta

func (r *Route) LookupMeta(key string) (any, bool)

LookupMeta searches for a metadata value by key, traversing the parent chain (route → group → parent group → ...) until found.

func (*Route) Middleware

func (r *Route) Middleware(m ...Middleware) *Route

Middleware appends one or more per-route middlewares. Must be called before the server starts; panics if called after compile. Returns the Route for fluent chaining.

For GET routes with an auto-generated HEAD twin, middleware is also appended to the twin so that HEAD requests run the same chain as GET — otherwise auth, rate limiting, and other middleware could be silently bypassed via HEAD.

func (*Route) Name

func (r *Route) Name(name string) *Route

Name sets the route name for URL generation. Route names must be unique within the App. Duplicate names panic. Must be called before the server starts; panics if called after compile. Returns the Route for fluent chaining.

func (*Route) SetMeta

func (r *Route) SetMeta(key string, val any) *Route

SetMeta attaches a key-value metadata pair to this route. Must be called before the server starts; panics if called after compile. Returns the Route for fluent chaining.

For GET routes with an auto-generated HEAD twin, the value is also set on the twin so middleware reading meta sees identical values regardless of which method was used.

type RouteContext

type RouteContext = radix.RouteContext

RouteContext is the routing context type used by the radix tree. Re-exported from internal/radix so consumers can reference this type without importing internal packages.

type RouteInfo

type RouteInfo struct {
	Method  string
	Pattern string
	Host    string // host pattern (empty for default mux)
}

RouteInfo holds introspection data about a registered route.

type Routes

type Routes interface {
	// Routes returns a slice of RouteInfo for all registered routes.
	Routes() []RouteInfo
}

Routes describes the interface for route introspection.

type Shutdowner

type Shutdowner interface {
	Shutdown(ctx context.Context) error
}

Shutdowner is implemented by services that need cleanup on shutdown. The context carries a deadline from the application's graceful shutdown timeout; implementations should respect ctx.Done() for timely cleanup.

type StaticCacheContext

type StaticCacheContext struct {
	// RequestPath is the path received from the client before internal rewrites.
	RequestPath string

	// FilePath is the resolved slash-separated path inside the fs.FS.
	// Directory listings use the resolved directory path.
	FilePath string

	// FileName is the base name of the served file, or the generated listing name.
	FileName string

	// IsHTML reports whether the response is an HTML entry point or listing.
	IsHTML bool
}

StaticCacheContext describes a successfully resolved static response for cache policy decisions.

type StaticConfig

type StaticConfig struct {
	// Index is the file served for directory requests.
	// Default: "index.html".
	Index string

	// Browse enables directory listing when a directory has no index file.
	// SPA takes precedence: if SPA is also true and the request qualifies
	// as an SPA candidate, the sibling-or-root fallback runs instead of
	// the directory listing.
	// Default: false (returns 404 for directories without index).
	Browse bool

	// SPA enables single-page application mode for routes that target a
	// page rather than an asset (GET or HEAD with no dot in the last path
	// segment). For these requests, the resolver prefers, in order:
	//
	//   File branch (path does not exist):
	//     1. <path>.html sibling (e.g. /admin/users → admin/users.html)
	//     2. root index
	//
	//   Directory branch (path is an existing directory):
	//     1. <dir>/<Index> (the directory's own index file)
	//     2. <dir>.html sibling (e.g. /reports/ → reports.html)
	//     3. root index
	//
	// Requests with a dot in the last segment (app.js, style.css) skip
	// the fallback entirely and return 404 when missing.
	// Default: false.
	SPA bool

	// Download sets Content-Disposition to "attachment" on all responses,
	// prompting the browser to download instead of display inline.
	// Default: false.
	Download bool

	// CacheControl decides the Cache-Control header for each successfully
	// served response (status below 400). It receives the resolved cache
	// context and returns the full header value; returning "" writes no
	// header. Nil disables Cache-Control entirely.
	//
	// The hook runs once per response — including 206 partial-content
	// responses — and never for error statuses. It should be pure and
	// deterministic.
	//
	// Presets cover the common policies:
	//
	//	credo.StaticConfig{CacheControl: credo.StaticCacheMaxAge(24 * time.Hour)}
	//	credo.StaticConfig{CacheControl: credo.StaticCacheImmutableAssets(365 * 24 * time.Hour)}
	//
	// Anything else is a few lines of code — e.g. SvelteKit-style builds
	// where only one directory holds content-hashed assets:
	//
	//	CacheControl: func(c credo.StaticCacheContext) string {
	//		if strings.HasPrefix(c.FilePath, "_app/immutable/") {
	//			return "public, max-age=31536000, immutable"
	//		}
	//		return "no-cache, must-revalidate"
	//	}
	CacheControl func(StaticCacheContext) string
}

StaticConfig configures static file serving for App.Static, Group.Static, App.File, and Group.File.

For App.File and Group.File, only Download and CacheControl apply. Setting Browse, SPA, or a non-empty Index panics at registration time.

Credo can only guard headers written before its Response.WriteHeader call. Reverse proxies or CDNs that add Cache-Control after Credo must be configured separately.

type StaticRoute

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

StaticRoute represents a static file serving endpoint. It wraps the two internal GET routes created by App.Static (catch-all + exact prefix match) and proxies fluent configuration to both. HEAD requests automatically observe the same middleware and route metadata as GET, so calling StaticRoute.Middleware or StaticRoute.SetMeta keeps the two methods in sync without any extra bookkeeping.

func (*StaticRoute) BuildURI

func (sr *StaticRoute) BuildURI(filePath ...string) string

BuildURI returns the URL path for a file within this static endpoint.

sr.BuildURI("css/app.css")  → "/static/css/app.css"
sr.BuildURI("")             → "/static/"
sr.BuildURI()               → "/static/"

func (*StaticRoute) Middleware

func (sr *StaticRoute) Middleware(m ...Middleware) *StaticRoute

Middleware appends middleware to both the catch-all and exact-match GET routes. HEAD requests automatically run the same chain as GET, so auth, rate limiting, and other header-mutating middleware can never be silently bypassed via a HEAD request.

Must be called before the server starts; panics if called after compile.

func (*StaticRoute) Name

func (sr *StaticRoute) Name(name string) *StaticRoute

Name sets the route name on the primary (catch-all) route only. Route names must be unique; the exact-match route remains unnamed. Use StaticRoute.BuildURI for URL generation instead of Route.BuildURI, which requires the catch-all parameter explicitly.

Must be called before the server starts; panics if called after compile.

func (*StaticRoute) SetMeta

func (sr *StaticRoute) SetMeta(key string, val any) *StaticRoute

SetMeta sets metadata on both the catch-all and exact-match GET routes. HEAD requests automatically see the same metadata as GET, so middleware reading route meta returns consistent values for both methods and both registration patterns.

Must be called before the server starts; panics if called after compile.

type StdMiddleware

type StdMiddleware = func(http.Handler) http.Handler

StdMiddleware is a stdlib-compatible middleware signature. Use WrapStdMiddleware to convert stdlib middleware into Middleware.

type WalkFunc

type WalkFunc func(method, pattern string) error

WalkFunc is the callback for Walk. It receives the method and pattern of each registered route. Return a non-nil error to stop walking.

type WalkRoutesFunc

type WalkRoutesFunc func(ri RouteInfo) error

WalkRoutesFunc is the callback for WalkRoutes. It receives the full RouteInfo including the host pattern. Return a non-nil error to stop walking.

Directories

Path Synopsis
Package auth provides authentication infrastructure for Credo applications.
Package auth provides authentication infrastructure for Credo applications.
Package config provides struct-centric configuration loading with deterministic merge order.
Package config provides struct-centric configuration loading with deterministic merge order.
Package httpclient provides outbound HTTP for Credo applications: timeout, retry with backoff, structured logging, and W3C trace context propagation — composed as an http.RoundTripper chain on a plain *http.Client.
Package httpclient provides outbound HTTP for Credo applications: timeout, retry with backoff, structured logging, and W3C trace context propagation — composed as an http.RoundTripper chain on a plain *http.Client.
internal
di
Adapted from github.com/samber/do (MIT License).
Adapted from github.com/samber/do (MIT License).
health
Package health defines the module-internal seam through which integration packages contribute store health results to the root health engine.
Package health defines the module-internal seam through which integration packages contribute store health results to the root health engine.
httpheader
Package httpheader provides comma-separated header token helpers shared by the root package's built-in middleware tier and the middleware package.
Package httpheader provides comma-separated header token helpers shared by the root package's built-in middleware tier and the middleware package.
i18n
Package i18n is the internal message lookup engine for the Credo framework.
Package i18n is the internal message lookup engine for the Credo framework.
observe
Package observe provides shared helpers for request observation code such as access logging and panic recovery.
Package observe provides shared helpers for request observation code such as access logging and panic recovery.
radix
Package radix implements a compressed radix tree (Patricia trie) for HTTP request routing.
Package radix implements a compressed radix tree (Patricia trie) for HTTP request routing.
requestid
Package requestid provides shared request ID helpers used by Credo's built-in and configurable middleware implementations.
Package requestid provides shared request ID helpers used by Credo's built-in and configurable middleware implementations.
Package middleware provides built-in HTTP middleware for the Credo framework.
Package middleware provides built-in HTTP middleware for the Credo framework.
Package pagination provides generic, ORM-agnostic types and utilities for paginated API responses.
Package pagination provides generic, ORM-agnostic types and utilities for paginated API responses.
Package store provides universal data access contracts for the Credo framework.
Package store provides universal data access contracts for the Credo framework.
Package testutil provides helpers for testing Credo applications: building a hermetic test App, overriding dependencies with fakes, injecting config, and asserting on structured log output.
Package testutil provides helpers for testing Credo applications: building a hermetic test App, overriding dependencies with fakes, injecting config, and asserting on structured log output.
Package validation provides a programmatic, type-safe validation engine using Go generics.
Package validation provides a programmatic, type-safe validation engine using Go generics.
Package worker provides background task management for Credo applications.
Package worker provides background task management for Credo applications.

Jump to

Keyboard shortcuts

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