# App Config in Go

The Node.js @app-config/cli library has built-in support for generating go code.

  1. Install it:

    npm i --save-dev @app-config/cli@2
    
  2. Add codegen instructions to the .app-config.meta.yml file:

    generate:
      - file: ./pkg/app-config.go
    
  3. Run the code generation:

    npx @app-config/cli gen
    
  4. Use the config module!

    port := GetConfig().Server.Port
    

This will result in a type-safe struct that represents your config faithfully. It also validates the config against the App Config schema.

Typically, users will wrap usage of their app in the @app-config/cli CLI. For example:

npx @app-config/cli -s -- go run .



The generated code will look something like this:

// @generated by app-config

package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"os"

	"github.com/xeipuuv/gojsonschema"
)

var config Config

func init() {
	loadedConfig, err := LoadConfig()

	if err != nil {
		log.Panic(err.Error())
	}

	config = loadedConfig
}

func GetConfig() Config {
	return config
}

func LoadConfig() (Config, error) {
	var loadedConfig Config
	var loadedSchema map[string]interface{}
	var err error

	configText := os.Getenv("APP_CONFIG")
	schemaText := os.Getenv("APP_CONFIG_SCHEMA")

	if configText == "" {
		return loadedConfig, errors.New("The APP_CONFIG environment variable was not set")
	}

	if schemaText == "" {
		return loadedConfig, errors.New("The APP_CONFIG_SCHEMA environment variable was not set")
	}

	err = json.Unmarshal([]byte(schemaText), &loadedSchema)

	if err != nil {
		return loadedConfig, fmt.Errorf("Could not parse APP_CONFIG_SCHEMA environment variable: %s", err.Error())
	}

	err = json.Unmarshal([]byte(configText), &loadedConfig)

	if err != nil {
		return loadedConfig, fmt.Errorf("Could not parse APP_CONFIG environment variable: %s", err.Error())
	}

	schemaLoader := gojsonschema.NewGoLoader(loadedSchema)
	documentLoader := gojsonschema.NewGoLoader(loadedConfig)

	result, err := gojsonschema.Validate(schemaLoader, documentLoader)

	if err != nil {
		return loadedConfig, fmt.Errorf("Could not validate App Config: %s", err.Error())
	}

	if !result.Valid() {
		errors := ""

		for _, desc := range result.Errors() {
			if errors == "" {
				errors = fmt.Sprintf("%v", desc)
			} else {
				errors = fmt.Sprintf("%s, %v", errors, desc)
			}
		}

		return loadedConfig, fmt.Errorf("The App Config value invalid: %s", errors)
	}

	return loadedConfig, nil
}

func UnmarshalConfig(data []byte) (Config, error) {
	var r Config
	err := json.Unmarshal(data, &r)
	return r, err
}

func (r *Config) Marshal() ([]byte, error) {
	return json.Marshal(r)
}

type Config struct {
	Jwt    Jwt    `json:"jwt"`
	Server Server `json:"server"`
}

type Jwt struct {
	Secret string `json:"secret"`
}

type Server struct {
	Port int64 `json:"port"`
}

You can specify the "no-singleton" option to avoid automatic (init function) config loading:

generate:
 - file: ./pkg/app-config.go
   rendererOptions:
     no-singleton: 'true'

Note that code generation is not guaranteed to be deterministic between versions of app config. We'll do our best to be stable though, and output code that gofmt is happy with.