Core Framework
Routing
Build routes on Go's ServeMux while preserving method handling and embedded public files.
Create the mux
Initialize the application context before creating routes:
ctx := appinit.Context(context.Background())
mux := lazyroutes.New(ctx)
appinit.Draw(ctx, mux)
lazyroutes.New creates an http.ServeMux and installs the embedded public
handler at /.
Draw application routes
All application routes are registered through:
func Draw(ctx context.Context, mux *http.ServeMux)
A route combines a Go 1.22+ method pattern with a bound controller action:
mux.Handle(
"GET /posts",
lazycontroller.Bind(
ctx,
posts.New,
(*posts.PostsController).Index,
),
)
The framework does not replace http.ServeMux. Standard-library pattern
precedence and conflict behavior still apply.
Path values
Declare path values in braces:
mux.Handle(
"GET /posts/{param}",
lazycontroller.Bind(
ctx,
posts.New,
(*posts.PostsController).Show,
),
)
Read the value from the request:
slug := r.PathValue("param")
Use GET /{$} for the root page only. Without {$}, / is a subtree pattern.
Method not allowed
Register an explicit method fallback for application paths:
mux.Handle(
"/posts",
lazyroutes.MethodNotAllowed(http.MethodGet),
)
The handler returns 405 Method Not Allowed and sets the Allow header.
Keep the allowed method list aligned with the method-specific routes for that path.
Public fallback
The application context installs the public handler:
ctx = lazyroutes.WithPublic(
ctx,
http.FileServerFS(public),
)
The root fallback accepts GET and HEAD. Other methods receive 405 Method Not Allowed.
Do not install another root public handler in Draw; doing so would duplicate
framework responsibility and can cause route conflicts.
Testing routes
Construct the complete handler without a network listener:
func application() http.Handler {
ctx := appinit.Context(context.Background())
mux := lazyroutes.New(ctx)
appinit.Draw(ctx, mux)
return mux
}
Use httptest.NewRequest and httptest.NewRecorder to verify status, headers,
and response bodies.