Start Here
Application Structure
Learn the conventional directories and ownership boundaries of a GoLazy application.
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 ordinary actions can set data and return nil
instead of passing a view path manually.
app/public
Public files are embedded, registered with lazyassets, and served after
application route lookup. For example:
app/public/styles.css
is available as:
/styles.css
/styles-<hash>.css
The logical path is useful for direct requests. Templates should call
asset_path to link the content-hashed permanent path. Explicit application
routes take precedence over public files.
cmd/app
The executable initializes the application and starts it with
lazyapp.App.ListenAndServe. The framework uses ADDR, then PORT, then
:3000 as the listen address.
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/lazycontrollerowns controller rendering and typed HTTP errors.golazy.dev/lazyroutesowns route scopes, route metadata, controller action routing, and REST resources.golazy.dev/lazydispatchowns middleware dispatch, route-only response buffering, dynamic route ETags, and request flow.golazy.dev/lazyassetsowns public and generated asset registration, fingerprints, integrity values, ETags, cache policy, and unpacking.golazy.dev/lazyappwires the application context, views, routes, dispatcher, and public files into one handler.golazy.dev/lazyviewowns rendering, helpers, and template engines.
Application packages must not be imported by the framework.