Customizing the runtime
app, err := runtime.New(runtime.Options{ Environment: os.Getenv("APP_ENV"), ErrorMapper: mapDomainError, HTTP: runtime.HTTPOptions{ Address: ":8080", TrustedProxies: []string{"10.0.0.0/8"}, CORSOrigins: []string{"https://app.example.com"}, },})if err != nil { return err}
app.Use(myMiddleware())app.OnShutdown(closeMetrics)return app.Run(ctx)The raw *gin.Engine and selected database handles remain available. Add
middleware, readiness checks, validation rules, translations, error mappers,
and shutdown hooks through options and interfaces. If the runtime itself must
change, fork the module and use a standard Go replace directive.
Testing handlers
Section titled “Testing handlers”The runtime/apptest package removes the recorder boilerplate from handler
tests while keeping assertions in plain Go:
app := apptest.New(t, runtime.Options{})app.Router().POST("/tasks", createTask)
var task Taskapp.POST("/tasks", newTask).RequireStatus(http.StatusCreated).Data(&task)See the Testing guide for request options, session/CSRF flows, integration databases, factories, and browser tests.
Feature flags
Section titled “Feature flags”Runtime applications can parse a small set of boolean feature flags
from FLAGS and wire the set explicitly:
featureFlags := flags.FromEnv()taskHandler := handler.NewTaskHandler(taskService, featureFlags)
if featureFlags.Enabled("task-export") { router.GET("/api/v1/tasks/export", taskHandler.Export)}FLAGS=task-export,new-dashboard enables both named features. The set is safe
for concurrent reads and updates, so an application-specific admin control can
call featureFlags.Set("task-export", false) at runtime. Changes are in-memory
only; gin-kit does not add a global flag registry, persistence, targeting, or
remote evaluation.
Standalone projects remain standalone and do not vendor this optional
package. When the same behavior is useful, copy this into
internal/platform/flags/flags.go and pass the set through constructors just
like any other dependency:
package flags
import ( "os" "sort" "strings" "sync")
type Set struct { mu sync.RWMutex values map[string]bool}
func New(names ...string) *Set { set := &Set{} for _, name := range names { set.Set(name, true) } return set}
func Parse(csv string) *Set { return New(strings.Split(csv, ",")...) }func FromEnv() *Set { return Parse(os.Getenv("FLAGS")) }
func (s *Set) Enabled(name string) bool { if s == nil { return false } s.mu.RLock() defer s.mu.RUnlock() return s.values[strings.TrimSpace(name)]}
func (s *Set) Set(name string, on bool) { if s == nil { return } name = strings.TrimSpace(name) if name == "" { return } s.mu.Lock() defer s.mu.Unlock() if on { if s.values == nil { s.values = make(map[string]bool) } s.values[name] = true } else { delete(s.values, name) }}
func (s *Set) Names() []string { if s == nil { return []string{} } s.mu.RLock() defer s.mu.RUnlock() names := make([]string, 0, len(s.values)) for name := range s.values { names = append(names, name) } sort.Strings(names) return names}
