App

Application Startup

Trace how a GoLazy application is constructed and started.

By Guillermo Alvarez - Published - Updated

Start from main

The executable entry point stays small:

func main() {
    if err := appinit.App().ListenAndServe(); err != nil {
        log.Fatal(err)
    }
}

App() returns a fully constructed application. ListenAndServe starts the HTTP server.

Construct the app

init/app.go calls lazyapp.New:

func App() *lazyapp.App {
    return lazyapp.New(lazyapp.Config{
        Name:         "sample_app",
        Drawer:       Draw,
        Public:       app.Public,
        Views:        app.Views,
        Dependencies: Dependencies,
    })
}

That call initializes application dependencies, optional jobs, SEO defaults, the renderer, route scope, dispatcher, asset registry, helpers, and public asset fallback.

Initialize shared dependencies

Dependencies runs once during application construction:

func Dependencies(deps *lazydeps.Scope) error {
    _, err := lazydeps.Service(deps, "helloworldservice", func(ctx context.Context) (
        context.Context,
        helloworldservice.Service,
        error,
        context.CancelFunc,
    ) {
        service := helloworldservice.New()
        return helloworldservice.WithContext(ctx, service), service, nil, nil
    })
    return err
}

Controllers resolve shared dependencies from this context during request-local construction.

Initialize background jobs

lazyapp.Config.Jobs runs after Dependencies with the dependency-initialized app context. It creates a lazyjobs.JobRunner, registers the app's job definitions, injects the runner into the app context, and starts in-process workers:

func App() *lazyapp.App {
    return lazyapp.New(lazyapp.Config{
        Name:         "sample_app",
        Drawer:       Draw,
        Public:       app.Public,
        Views:        app.Views,
        Dependencies: Dependencies,
        Jobs: lazyapp.Jobs(lazyjobs.Config{
            Define: jobs.DefinedJobs,
        }),
    })
}

Read Background Jobs.

Initialize SEO defaults

lazyapp.Config.SEO runs after Dependencies, so application-wide metadata defaults can read dependency-backed values from the app context:

func App() *lazyapp.App {
    return lazyapp.New(lazyapp.Config{
        Name:         "sample_app",
        Drawer:       Draw,
        Public:       app.Public,
        Views:        app.Views,
        Dependencies: Dependencies,
        SEO:          SEO,
    })
}

Keep that function in init/seo.go. Read SEO And Sitemaps.

Draw routes

The app passes a framework-created route scope to Draw:

func Draw(router *lazyroutes.Scope) {
    router.Resources(homecontroller.New, func(home *lazyroutes.Resource) {
        home.Singular("home")
        home.Plural("home")
        home.Path("")
    })
}

Draw only registers routes. It does not create the mux, dispatcher, public fallback, or server.

Choose the address

ListenAndServe reads ADDR, then PORT, then falls back to 127.0.0.1:3000:

ADDR=127.0.0.1:4000 ./sample-app
PORT=4000 ./sample-app

A numeric ADDR or PORT value is treated as a port. A full address is passed to the underlying server.

CONTROL_PLANE_ADDR activates the default control plane during ListenAndServe. If it matches the app address, probes are mounted into the app server. If it differs, GoLazy starts a second server for the control plane. The separate server automatically includes /debug/pprof/ and the standard pprof subpaths. If it is unset, production builds do not mount control-plane endpoints on the public app listener.