API Reference (Go)
This document is a concise but complete API map of the current Phase 0 implementation. It covers every Go source file currently present in the repository and explains how each part contributes to the BFF skeleton.
1) What BFF means in this repository
Short version
BFF (Backend for Frontend) in this framework is a thin server-side layer that:
- prepares data for the UI (via CQRS and i18n content),
- renders pages with
templ, - acts as a single HTTP entry point (
net/http), - owns cross-cutting concerns (request ID, panic recovery, logging, localization, theme state),
- does not contain domain business UI logic itself.
Why this is useful here
- Fast path to an MVP dashboard without SPA complexity.
- Clear separation: presentation in
internal/site/web/*, use-cases ininternal/application/*, and infrastructure adapters ininternal/infra/*, plus platform core inpkg/*. - Unified entry point for future JSON/SPA/API endpoints (Phase 1+).
Current request flow
HTTP -> middleware chain -> feature route -> CQRS query -> i18n -> templ layout/page -> Render
Where to find layer boundaries
pkg/app,pkg/core,pkg/web: framework core and platform API.internal/application/*andinternal/infra/features/*: use-case + adapter examples.internal/site/web/views/*: presentation layer (templ components).
2) Application vs Infra feature contract
Feature code in this repository is split into two contracts:
internal/application/<feature>
- Contains CQRS use cases and business orchestration for the feature.
- Owns query/command types, handlers, and local DTOs returned to the transport layer.
- Must not depend on
pkg/web,internal/site/web/views, or generated*_templ.gofiles. - Example:
internal/application/welcome/handler.go- exports
WelcomeQuery,WelcomeQueryResult,WelcomeQueryHandler
internal/infra/features/<feature>
- Contains HTTP/templ transport adapters and feature registration.
- Implements
pkg/app.Feature(Routes,NavItems, optionalNavProvider). - Accepts a
*cqrs.Dispatcherin constructor and dispatches application queries/commands. - May import
pkg/webandinternal/site/web/viewsfor transport concerns (rendering, localization switches, route handling). - Example:
internal/infra/features/welcome/module.go- calls
cqrs.DispatchQuery[appwelcome.WelcomeQuery, appwelcome.WelcomeQueryResult](...)
Wiring rule (root composition)
cmd/server/main.goowns registration:- register application handlers in
cqrsfirst (cqrs.RegisterQuery/RegisterCommand); - create feature adapters and pass them into
app.New(...).WithFeature(...).Build().
- register application handlers in
Featurecode should not contain direct persistence or presentation logic beyond route handling.
New feature checklist
- Add use-case files in
internal/application/<feature>/. - Add transport adapter in
internal/infra/features/<feature>/implementingpkg/app.Feature. - Import and register handler and adapter in
cmd/server/main.go. - Add templates and static assets under
internal/site/web/*as needed. - Keep import direction one-way:
main -> infra -> application -> core/platform.
3) File-by-file API map
Below is the API you can use as a quick reference.
cmd/server/main.go
package main
Exported API
func main()- Loads configuration with
app.LoadConfig(). - Initializes
cqrs.Dispatcherwith behaviors:behaviors.Loggingbehaviors.Validation
- Registers
welcome.WelcomeQueryHandler. - Builds the app through
app.New(...).WithFeature(...).Build(). - Starts the app with graceful shutdown context (
signal.NotifyContext).
- Loads configuration with
Behavior Notes
- Exits with code
1on configuration error or fatal server stop. application.Run(ctx)manages the HTTP server lifecycle.
pkg/app/config.go
package app
Types
type Config structAppBind stringDataSource stringStaticDir stringDefaultLocale stringAvailableLocales []string
Functions
func LoadConfig() (Config, error)- Reads env values:
APP_BINDAPP_DATA_SOURCEAPP_STATIC_DIRAPP_DEFAULT_LOCALEAPP_AVAILABLE_LOCALES
- Returns an error if no locales are configured.
- Reads env values:
func getEnv(key string, fallback string) string(unexported)func parseLocales(raw string) []string(unexported)
pkg/app/feature.go
package app
Types
type NavItem struct { Label, Path, Icon string; Order int }type Feature interfaceID() stringRoutes(mux *http.ServeMux)NavItems() []NavItem
Notes
- In Phase 0,
Featureis intentionally minimal and focused on route registration and navigation metadata.
pkg/app/builder.go
package app
Types
type App structcfg Configmux *http.ServeMuxfeatures []Featurehandler http.HandlernavItems []NavItem
type NavProvider interfaceSetNavItems([]NavItem)
type AppBuilder structcfg Configfeatures []Featuremux *http.ServeMux
Methods / Functions
func (a *App) ServeHTTP(w http.ResponseWriter, r *http.Request)- Implements
http.Handler.
- Implements
func (a *App) Handler() http.Handlerfunc (a *App) NavItems() []NavItemfunc (a *App) Run(ctx context.Context) error- Starts
http.Serverona.cfg.AppBind. - Supports graceful shutdown with a 5s timeout.
- Starts
func New(cfg Config) *AppBuilderfunc (b *AppBuilder) WithFeature(feature Feature) *AppBuilderfunc (b *AppBuilder) Build() *App- Builds middleware chain (
RequestID,Recover,Logger). - Registers feature routes.
- Injects shared navigation via
SetNavItemswhen feature implementsNavProvider. - Adds static file serving at
/static/.
- Builds middleware chain (
func collectNavItems(features []Feature) []NavItem(unexported)- Aggregates and stable-sorts nav items.
pkg/core/entity.go
package core
Types / Functions
type Entity[ID comparable] struct { ID ID; CreatedAt time.Time; UpdatedAt time.Time }func NewEntity[ID comparable](id ID) Entity[ID]func (e *Entity[ID]) Touch()
pkg/core/errors.go
package core
Types / Constants
type ErrorCode stringErrorCodeNotFound,ErrorCodeConflict,ErrorCodeValidation,ErrorCodeUnauthorized,ErrorCodeForbidden,ErrorCodeInternal
type DomainError struct { Code ErrorCode; Message string; Cause error }
Functions / Methods
func NewDomainError(code ErrorCode, message string) DomainErrorfunc WrapDomainError(code ErrorCode, message string, cause error) DomainErrorfunc (e DomainError) Error() stringfunc (e DomainError) StatusCode() int
pkg/core/cqrs/command.go
package cqrs
Types
type Command interface{}type Query interface{}type CommandHandler[T any, R any] interface { Handle(context.Context, T) (R, error) }type QueryHandler[T any, R any] interface { Handle(context.Context, T) (R, error) }
pkg/core/cqrs/behavior.go
package cqrs
Types
type HandlerFunc func(context.Context, any) (any, error)type PipelineBehavior interface { Handle(ctx context.Context, request any, next HandlerFunc) (any, error) }type HandlerNotFoundError struct { RequestType string }func (e HandlerNotFoundError) Error() string
pkg/core/cqrs/dispatcher.go
package cqrs
Types
type Dispatcher structcommandHandlers map[string]HandlerFuncqueryHandlers map[string]HandlerFuncbehaviors []PipelineBehavior
Methods / Functions
func NewDispatcher(behaviors ...PipelineBehavior) *Dispatcherfunc (d *Dispatcher) RegisterCommandHandler(requestType string, handler HandlerFunc)func (d *Dispatcher) RegisterQueryHandler(requestType string, handler HandlerFunc)func (d *Dispatcher) Dispatch(ctx context.Context, request any) (any, error)- Resolves request handler from command/query maps and applies behavior pipeline.
- Returns
core.WrapDomainError(... HandlerNotFoundError...)when missing.
func (d *Dispatcher) execute(ctx context.Context, _ string, handler HandlerFunc, request any) (any, error)(unexported)func wrapBehavior(behavior PipelineBehavior, next HandlerFunc) HandlerFunc(unexported)
pkg/core/cqrs/handler.go
package cqrs
Functions
func requestKey(request any) string(unexported)func RegisterCommand[T any, R any](dispatcher *Dispatcher, handler CommandHandler[T, R])func RegisterQuery[T any, R any](dispatcher *Dispatcher, handler QueryHandler[T, R])func DispatchCommand[T any, R any](ctx context.Context, dispatcher *Dispatcher, command T) (R, error)func DispatchQuery[T any, R any](ctx context.Context, dispatcher *Dispatcher, query T) (R, error)func dispatchTyped(ctx context.Context, dispatcher *Dispatcher, request any, _ string) (any, error)(unexported)func wrapHandler[T any](fn func(context.Context, T) (any, error)) HandlerFunc(unexported)
pkg/core/cqrs/behaviors/logging.go
package behaviors
Types
type Logging struct { Logger *slog.Logger }
Methods
func (l Logging) Handle(ctx context.Context, request any, next cqrs.HandlerFunc) (any, error)- Logs CQRS request start/completion/errors.
- Uses
slog.Default()ifLoggeris nil.
pkg/core/cqrs/behaviors/validation.go
package behaviors
Types
type Validation struct{}
Methods
func (Validation) Handle(ctx context.Context, request any, next cqrs.HandlerFunc) (any, error)- If request has
Validate() error, calls it beforenext. - On validation failure, returns
core.WrapDomainError(ErrorCodeValidation, ...).
- If request has
pkg/web/page.go
package web
Types
type ThemeToggleData structtype PageData structtype LanguageToggle struct
Used for passing view-state into templates.
pkg/web/render.go
package web
Functions
func Render(ctx context.Context, w http.ResponseWriter, component templ.Component) error- Renders templ component into a response buffer.
- Sets
Content-Type: text/html.
func WriteJSON(w http.ResponseWriter, status int, payload any) error- Serializes payload as JSON with the specified status.
pkg/web/error_handler.go
package web
Functions
func HandleError(w http.ResponseWriter, err error)- Maps
core.DomainErrorto an HTTP status. - Logs through
slog.Error. - Sends a generic response via
http.Error.
- Maps
pkg/web/middleware/chain.go
package middleware
Types
type Middleware func(http.Handler) http.Handlertype Chain []Middleware
Methods
func (c Chain) Then(h http.Handler) http.Handler- Applies middleware in reverse order.
pkg/web/middleware/request_id.go
package middleware
Types / constants
const RequestIDHeader = "X-Request-ID"
Functions
func ContextWithRequestID(ctx context.Context, requestID string) context.Contextfunc RequestIDFromContext(ctx context.Context) stringfunc RequestIDMiddleware() Middleware- If the header is missing, generates
uuid.NewString()and writes it to both context and response.
- If the header is missing, generates
pkg/web/middleware/recover.go
package middleware
Functions
func RecoverMiddleware() Middleware- Recovers panics and returns HTTP 500.
pkg/web/middleware/logger.go
package middleware
Types
type statusResponseWriter structstatusCode intsize int
func (rw *statusResponseWriter) WriteHeader(statusCode int)func (rw *statusResponseWriter) Write(b []byte) (int, error)
Methods
func LoggerMiddleware() Middleware- Logs
http.requestandhttp.responsewithrequest_id,status,duration_ms, andsize.
- Logs
internal/site/web/i18n/embed.go
package i18n
Types
type Localized struct { Common CommonFixture; Welcome WelcomeFixture }type CommonFixture struct,NavFixture,ThemeFixture,LangFixture,WelcomeFixture
Functions
func Locales() []stringfunc Decode[T any](locale string, section string) (T, error)func Load(locale string) (Localized, error)func normalizeLocale(locale string) string(unexported)
Globals
var fixtureFS embed.FSpopulated by//go:embed en/*.json ru/*.json.
internal/application/welcome/handler.go
package welcome
Types
type WelcomeQuery struct { Locale string }func (q WelcomeQuery) Validate() error
type WelcomeQueryResult struct { Layout i18n.Localized }type WelcomeQueryHandler struct{}func (h WelcomeQueryHandler) Handle(_ context.Context, query WelcomeQuery) (WelcomeQueryResult, error)- Loads i18n fixtures and returns localized payload.
internal/infra/features/welcome/module.go
package welcome
Types
type Module structdispatcher *cqrs.DispatchernavItems []app.NavItemdefaultLocale stringavailableLocales []string
Methods
func New(dispatcher *cqrs.Dispatcher, defaultLocale string, availableLocales []string) *Modulefunc (m *Module) ID() stringfunc (m *Module) NavItems() []app.NavItemfunc (m *Module) SetNavItems(items []app.NavItem)func (m *Module) Routes(mux *http.ServeMux)func (m *Module) handleWelcome(w http.ResponseWriter, r *http.Request)- Resolves locale from
?lang=query param.
- Resolves locale from
func resolveLocale(r *http.Request, defaultLocale string, allowedLocales []string) string(unexported)- Normalizes locale, validates allowed values, and falls back to default.
func containsLocale(allowedLocales []string, locale string) bool(unexported)
internal/site/web/views/models.go
package views
Types
type ThemeToggleData structtype LayoutData structtype WelcomePageData struct
internal/site/web/views/partials/models.go
package partials
Types
type LanguageToggleData struct
internal/site/web/views/layout_templ.go (generated)
package views
Functions
func Layout(data LayoutData, headExtra templ.Component, body templ.Component) templ.Componentfunc asShellNavItems(items []app.NavItem) []ui8layout.NavItem
Notes
- Generated by
templ; this is the runtime template component API.
internal/site/web/views/welcome_templ.go (generated)
package views
Function
func WelcomePage(data WelcomePageData) templ.Component
internal/site/web/views/nav_templ.go (generated)
package views
Functions
func layoutHeadExtra(headExtra t.Component) templ.Component
internal/site/web/views/partials/language_toggle_templ.go (generated)
package partials
Function
func LanguageToggle(data LanguageToggleData) templ.Component
4) How to use this API map during development
-
To add a new feature:
- Define query/command and handler in
internal/application/<name>/. - Register the handler in
main.gousingcqrs.RegisterQuery/cqrs.RegisterCommand. - Implement
Featureadapter ininternal/infra/features/<name>/withRoutesandNavItems. - Add DTOs/i18n resources and templates.
- Integrate localization and error handling via
internal/site/web/i18n+web.HandleError.
- Define query/command and handler in
-
For troubleshooting:
- Follow flow:
features -> handler -> query -> i18n -> views. - Middleware chain always starts with request ID for traceability.
- Prefer returning business errors as
core.DomainError.
- Follow flow:
5) Extension notes
- In Phase 1, expected next API work includes:
- health/readiness endpoints,
- additional middleware (auth, rate limiting, metrics),
- API envelope/router,
- persistence and caching abstractions.
- These extensions are not yet reflected in this file because the current phase intentionally remains minimal.