Controllers
Controller Architecture
Use request-local controllers with uniform constructors and automatic rendering.
Shape a controller
A concrete controller embeds the application base controller and keeps only the services it needs:
type PostsController struct {
controllers.BaseController
posts *postservice.Service
}
The constructor receives the application context:
func New(ctx context.Context) (*PostsController, error) {
base, err := controllers.NewBaseController(ctx)
if err != nil {
return nil, err
}
posts, ok := postservice.FromContext(ctx)
if !ok {
return nil, fmt.Errorf("posts service missing")
}
return &PostsController{
BaseController: base,
posts: posts,
}, nil
}
Do not add renderer, response writer, request, service, or view-path parameters to concrete controller constructors.
Keep controllers request-local
Routes point at a constructor, not a shared controller instance:
router.Get("/posts", posts.New, (*posts.PostsController).Index)
For each request, GoLazy constructs a fresh controller, runs the selected
action, and then renders if the action did not already write a response.
Mutable state such as Set, headers, layout selection, and status code stays
on that one request.
Write standard actions
The standard action signature is:
func (c *PostsController) Index(
w http.ResponseWriter,
r *http.Request,
) error
Most HTML actions can ignore w, set template data, and return nil:
func (c *PostsController) Index(_ http.ResponseWriter, _ *http.Request) error {
c.Set("title", "Posts")
c.Set("posts", c.posts.List())
return nil
}
GoLazy renders the default controller/action view after the action returns.
Use Render("other") only when an action should render a different view.
Return expected errors
Use lazycontroller.Error for expected HTTP failures:
post, ok := c.posts.Get(r.PathValue("post_id"))
if !ok {
return lazycontroller.Error(
http.StatusNotFound,
fmt.Errorf("post not found"),
)
}
c.Set("post", post)
return nil
Unexpected errors become 500 Internal Server Error. The wrapped error is
kept for callers and future logging, while the user receives the status
response.
Set response metadata
Response helpers record metadata without committing the response early:
c.Status(http.StatusCreated)
c.Header().Set("Cache-Control", "no-store")
c.Set("post", post)
return nil
Automatic rendering can still run because Status does not call
WriteHeader. If an action writes the response body manually, use the
standard http.ResponseWriter methods directly.
Use Generators for typed action arguments and MIME and Formats for multi-format actions.