Skip to main content
This guide will walk you through creating a simple Go application using Salt’s configuration management and logging packages.

Prerequisites

  • Go 1.22 or later installed
  • Basic familiarity with Go modules

Step-by-step guide

1

Create a new Go project

Create a new directory and initialize a Go module:
mkdir my-salt-app
cd my-salt-app
go mod init example.com/my-salt-app
2

Install Salt

Add Salt to your project:
go get github.com/raystack/salt
3

Create your configuration

Create a config.yaml file in your project root:
config.yaml
app:
  name: "my-app"
  environment: "development"
  port: 8080

database:
  host: "localhost"
  port: 5432
  name: "mydb"

log:
  level: "info"
4

Create your application config struct

Create a main.go file and define your configuration struct:
main.go
package main

import (
    "fmt"
    
    "github.com/raystack/salt/config"
    "github.com/raystack/salt/log"
)

type AppConfig struct {
    App struct {
        Name        string `mapstructure:"name" default:"app"`
        Environment string `mapstructure:"environment" default:"development"`
        Port        int    `mapstructure:"port" default:"8080" validate:"required,min=1,max=65535"`
    } `mapstructure:"app"`
    
    Database struct {
        Host string `mapstructure:"host" default:"localhost"`
        Port int    `mapstructure:"port" default:"5432"`
        Name string `mapstructure:"name" validate:"required"`
    } `mapstructure:"database"`
    
    Log struct {
        Level string `mapstructure:"level" default:"info"`
    } `mapstructure:"log"`
}
5

Load configuration and initialize logging

Add the main function to load configuration and set up logging:
main.go
func main() {
    // Create configuration loader
    loader := config.NewLoader(
        config.WithFile("./config.yaml"),
        config.WithEnvPrefix("MYAPP"),
    )
    
    // Load configuration
    var cfg AppConfig
    if err := loader.Load(&cfg); err != nil {
        panic(fmt.Sprintf("failed to load config: %v", err))
    }
    
    // Initialize logger
    logger := log.NewLogrus(
        log.LogrusWithLevel(cfg.Log.Level),
    )
    
    // Log application startup
    logger.Info("application starting", 
        "name", cfg.App.Name,
        "environment", cfg.App.Environment,
        "port", cfg.App.Port,
    )
    
    logger.Info("database configuration",
        "host", cfg.Database.Host,
        "port", cfg.Database.Port,
        "name", cfg.Database.Name,
    )
    
    // Your application logic here
    logger.Info("application ready")
}
6

Run your application

Run your application:
go run main.go
You should see output like:
INFO[0000] application starting environment=development name=my-app port=8080
INFO[0000] database configuration host=localhost port=5432 name=mydb
INFO[0000] application ready
Try overriding configuration with environment variables:
MYAPP_APP_PORT=9090 MYAPP_LOG_LEVEL=debug go run main.go

Configuration precedence

Salt uses the following configuration precedence (highest to lowest):
  1. Command-line flags (if WithFlags() is used)
  2. Environment variables (with the specified prefix)
  3. Configuration file (YAML/JSON)
  4. Default values (from struct tags)
This allows you to:
  • Use sensible defaults for development
  • Override with config files for different environments
  • Override specific values with environment variables in production
  • Override everything with command-line flags for testing

Next steps

Now that you have a basic Salt application running, explore more features:

Configuration Package

Learn about advanced configuration features like dynamic flag binding and validation

Logging Package

Explore structured logging with Logrus and Zap

Database Package

Add database connectivity with connection pooling and migrations

CLI Utilities

Build powerful CLI tools with commander, printer, and prompter

Complete example

Here’s the complete working example:
package main

import (
    "fmt"
    
    "github.com/raystack/salt/config"
    "github.com/raystack/salt/log"
)

type AppConfig struct {
    App struct {
        Name        string `mapstructure:"name" default:"app"`
        Environment string `mapstructure:"environment" default:"development"`
        Port        int    `mapstructure:"port" default:"8080" validate:"required,min=1,max=65535"`
    } `mapstructure:"app"`
    
    Database struct {
        Host string `mapstructure:"host" default:"localhost"`
        Port int    `mapstructure:"port" default:"5432"`
        Name string `mapstructure:"name" validate:"required"`
    } `mapstructure:"database"`
    
    Log struct {
        Level string `mapstructure:"level" default:"info"`
    } `mapstructure:"log"`
}

func main() {
    // Create configuration loader
    loader := config.NewLoader(
        config.WithFile("./config.yaml"),
        config.WithEnvPrefix("MYAPP"),
    )
    
    // Load configuration
    var cfg AppConfig
    if err := loader.Load(&cfg); err != nil {
        panic(fmt.Sprintf("failed to load config: %v", err))
    }
    
    // Initialize logger
    logger := log.NewLogrus(
        log.LogrusWithLevel(cfg.Log.Level),
    )
    
    // Log application startup
    logger.Info("application starting", 
        "name", cfg.App.Name,
        "environment", cfg.App.Environment,
        "port", cfg.App.Port,
    )
    
    logger.Info("database configuration",
        "host", cfg.Database.Host,
        "port", cfg.Database.Port,
        "name", cfg.Database.Name,
    )
    
    // Your application logic here
    logger.Info("application ready")
}
app:
  name: "my-app"
  environment: "development"
  port: 8080

database:
  host: "localhost"
  port: 5432
  name: "mydb"

log:
  level: "info"