Start Here

Application Structure

Learn the conventional directories and ownership boundaries of a GoLazy application.

By Guillermo Alvarez - Published - Updated

The application tree

A GoLazy application keeps its executable small and places application behavior under app:

app/
  controllers/
  helpers/
  public/
  services/
  views/
cmd/app/
init/
lib/
test/

The structure is conventional, but every directory contains ordinary Go, standard-library templates, or static files.

app/controllers

Controllers translate HTTP requests into application work. A concrete controller embeds the application BaseController, resolves its services in New, and exposes action methods.

Controller constructors receive only context.Context:

func New(ctx context.Context) (*PostsController, error)

Controllers are request-local. Never cache a concrete controller or share it between requests because render data and response state are mutable.

app/helpers

Helpers contain template functions owned by the application. Keep them as plain Go functions and expose the template names through RegisterHelpers:

func RegisterHelpers() map[string]any

init/app.go passes that map to lazyapp.Config.Helpers, and templates call helpers by their registered names.

init

init/app.go is the application composition entry point. It calls lazyapp.New, passes embedded views and public files, provides the app context initializer, and points the framework at Draw.

init/context.go initializes shared application dependencies once and places them into a context.

init/routes.go is the routing table. Its public entry point is:

func Draw(router *lazyroutes.Scope)

Draw receives the framework-created route scope. It does not create or return the application handler, and it does not install dispatch middleware.

app/services

Services contain application behavior that does not belong to HTTP handling or template rendering. Each service defines typed WithContext and FromContext helpers so its dependency contract stays visible.

Services should be deterministic where practical and independently testable.

app/views

Views use Go's html/template syntax:

app/views/layouts/<layout>.html.tpl
app/views/<controller>/<action>.html.tpl

The default layout is layouts/app.html.tpl. Controller view names are derived from route metadata, so controllers do not pass a view path manually.

app/public

Public files are embedded and served by the dispatcher after application route lookup. For example:

app/public/styles.css

is available as:

/styles.css

Explicit application routes take precedence over public files.

cmd/app

The executable initializes the application and owns process-level concerns such as the listen address and http.Server.

Keep business logic out of main. It should remain possible to construct the complete handler in tests without starting a network listener.

lib

Application-specific adapters can live in lib. The sample app keeps its Goldmark adapter in lib/markdown, leaving the framework independent of that third-party dependency.

test

Package tests remain next to their code. Full application integration tests live in test and construct the same application handler used by the executable.

Framework boundaries

Generic behavior belongs in the golazy.dev module:

  • golazy.dev/lazycontroller owns controller rendering and typed HTTP errors.
  • golazy.dev/lazyroutes owns route scopes, route metadata, controller action routing, and REST resources.
  • golazy.dev/lazydispatch owns middleware dispatch, public-file fallback, and request flow.
  • golazy.dev/lazyapp wires the application context, views, routes, dispatcher, and public files into one handler.
  • golazy.dev/lazyview owns rendering, helpers, and template engines.

Application packages must not be imported by the framework.