Skip to content

Application and Configurator

Every Nago program has the same shape:

func main() {
	application.Configure(func(cfg *application.Configurator) {
		cfg.SetApplicationID("com.example.myapp")
		cfg.Serve(vuejs.Dist())

		// enable systems, create services, register root views ...
	}).Run()
}

Lifecycle

  1. application.Configure loads a .env file from the working directory, if one exists, reads the environment variables and then calls your function with a fresh *application.Configurator.
  2. In your function you configure everything. This happens exactly once, before the server starts, so it is the place to create stores, enable systems and wire your use cases.
  3. Run applies pending migrations, starts the web server and blocks until the process receives SIGINT or SIGTERM. Then it shuts the application down and calls the functions registered with cfg.OnDestroy.

Errors during configuration are usually fatal. The tutorials therefore unwrap results with std.Must from go.wdy.de/nago/pkg/std, or option.Must and option.MustZero from github.com/worldiety/option, which panic on an error.

What you configure

ConcernConfigurator methods
identitySetApplicationID, SetName, SetVersion, SetSemanticVersion, AppIcon
frontendServe(vuejs.Dist())
pagesRootView, RootViewWithDecoration, SetDecorator, NewScaffold, see Navigation
static resourcesResource, e.g. for embedded images
dataDataDir, EntityStore, FileStore, NDB, see Persistence
themeColorSet, ThemeManagement, see Theming
systemsStandardSystems, UserManagement, SessionManagement, …, see Systems
servicesAddContextValue, Context, EventBus
serverSetHost, SetFPS, SetContextPath, Debug

Static resources

Embed files with go:embed into an application.StaticBytes and register them with cfg.Resource. You get a URI which you can pass to views such as Image().URI(...):

//go:embed logo.jpg
var logo application.StaticBytes

// within Configure
logoURI := cfg.Resource(logo)

See tutorial-02-combining-views.

Systems

Nago’s built-in features are organized as systems. Each one is enabled by a method of the configurator or by an Enable function in a cfg package below application/:

option.MustZero(cfg.StandardSystems())                 // admin center, users, sessions, mail, backup, ...
users := std.Must(cfg.UserManagement())                // a single system, returns its use cases and pages
std.Must(cfgscheduler.Enable(cfg))                     // go.wdy.de/nago/application/scheduler/cfg

Enabling a system is idempotent: calling the method again returns the already configured instance. Systems enable the systems they depend on themselves. StandardSystems enables the image, admin, user, backup, mail, secret, template and session management. Each system returns a struct with its UseCases, which you can call from your own code. The Systems section describes them one by one.

Your own services

Prefer to create your use cases in Configure and pass them explicitly to the views which need them. A closure over a local variable is all you need:

sayHello := NewSayHello()

cfg.RootView(".", func(wnd core.Window) core.View {
	return ui.Text(sayHello(wnd.Subject()))
})

For loosely coupled components, the configurator also provides a small service registry. Register a value with cfg.AddContextValue(core.ContextValue("", myService)) and look it up by its exact type with core.FromContext[MyService](wnd.Context(), ""). The built-in systems use this mechanism, e.g. to find the optional mail system. Use it sparingly, explicit wiring is easier to follow.

cfg.EventBus() returns an application-wide event bus to publish and subscribe to domain events, see tutorial-47-eventbus.

Without a web server

cfg.OneShot() makes Run return right after the configuration instead of starting the server. This is useful for command line tools which reuse the configuration and data of an application, like the admin reset tool.

Related