CLI Integration
Okapi comes with a built-in command-line interface (CLI) helper that makes it easy to configure and run your application from the terminal.
It supports:
- Typed flags (
string,int,bool,float,time.Duration, etc.) - Short and long options
- Environment variable binding
- Default values
- Struct-based flag generation
- Graceful lifecycle hooks (start / started / shutdown)
Basic Usage
app := okapi.New()
// Create CLI instance
cli := okapicli.New(app, "myapp").
String("config", "c", "config.yaml", "Path to configuration file").
Int("port", "p", 8080, "HTTP server port").
Bool("debug", "d", false, "Enable debug mode")
// Parse flags
if err := cli.Parse(); err != nil {
panic(err)
}
// Retrieve flag values
port := cli.GetInt("port")
debug := cli.GetBool("debug")
app.WithPort(port)
app.WithDebug(debug)
app.Get("/", func(ctx *okapi.Context) error {
return ctx.OK(okapi.M{
"status": "ok",
"message": "CLI example",
})
})
// Run server with lifecycle hooks
if err := cli.RunServer(&okapicli.RunOptions{
OnStart: func() {
slog.Info("Preparing resources before startup")
},
OnStarted: func() {
slog.Info("Server started successfully")
},
OnShutdown: func() {
slog.Info("Cleaning up before shutdown")
},
}); err != nil {
panic(err)
}
Example CLI Output
$ ./myapp --help
Usage:
myapp [flags]
Flags:
-c, --config string Path to configuration file (default "config.yaml")
-p, --port int HTTP server port (default 8080)
-d, --debug Enable debug mode
Struct Tag Support (Automatic Flag Generation)
Instead of manually defining each flag, you can generate flags directly from a struct using tags.
Supported Tags
| Tag | Description |
|---|---|
cli | Flag name (required) |
short | Short flag name (optional) |
desc | Description shown in --help |
default | Default value (string, parsed to type) |
env | Environment variable name |
Example
type ServerConfig struct {
Port int `cli:"port" short:"p" desc:"HTTP server port" env:"APP_PORT" default:"8080"`
Host string `cli:"host" short:"h" desc:"Server hostname" env:"APP_HOST" default:"localhost"`
Debug bool `cli:"debug" short:"d" desc:"Enable debug mode" env:"APP_DEBUG"`
Config string `cli:"config" short:"c" desc:"Path to config file" default:"config.yaml"`
}
func main() {
cfg := &ServerConfig{
Port: 8000, // Struct defaults (lowest priority)
}
o := okapi.New()
cli := okapicli.New(o, "MyApp").
FromStruct(cfg) // or WithConfig(cfg)
// Parse: env first, then CLI args (CLI overrides env)
if err := cli.Parse(); err != nil {
panic(err)
}
slog.Info("Starting server",
"port", cfg.Port,
"host", cfg.Host,
"debug", cfg.Debug,
"config", cfg.Config,
)
// Optional: access flags directly
port := cli.GetInt("port") // Same as cfg.Port
_ = port
}
Value Resolution Order
When using FromStruct, values are resolved in the following order (lowest → highest priority):
- Struct field default
defaulttag- Environment variable (
env) - CLI flag
Example:
Port:
Struct default → 8000
APP_PORT=9000 → overrides struct
--port=8080 → overrides env
One-Liner Parsing (Fail Fast)
If you prefer to panic on configuration errors and parse immediately:
o := okapi.New()
cfg := &ServerConfig{}
cli := okapicli.New(o, "MyApp").
FromStruct(cfg).
MustParse() // Panics on error
This is useful for small tools or when invalid configuration should stop startup immediately.
When to Use CLI vs Config Files
A common pattern is to combine CLI flags with configuration files:
- CLI → runtime overrides (ports, debug, mode)
- Config file → larger application configuration
Example:
./myapp --config=config.prod.yaml --port=9000 --debug
Load Config File
You can load configuration from a file (YAML, JSON, etc.).
o := okapi.New()
cfg := &Config{}
cli := okapicli.New(o, "MyApp")
if err := cli.LoadConfig("config.yaml", cfg); err != nil {
panic(err)
}