Core Framework
Controllers
Use request-local controllers to coordinate services, prepare view data, and return HTTP errors.
Controller shape
A concrete controller embeds the application's base controller and declares only the services it needs:
type PostsController struct {
controllers.BaseController
posts *postservice.Service
}
The constructor receives an 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 is missing from application context",
)
}
return &PostsController{
BaseController: base,
posts: posts,
}, nil
}
Do not add renderer, service, request, writer, or view-path parameters to concrete constructors. Those dependencies are resolved from context or fixed by the controller.
Request-local construction
Routes use scope methods:
router.Get(
"/posts",
posts.New,
(*posts.PostsController).Index,
)
For each request, the route action wrapper:
- Adds the response writer to the controller context.
- Calls the controller constructor.
- Runs the selected action.
- Renders the matching view when the action returns without writing a response.
- Converts any returned error into an HTTP response.
This lifecycle prevents mutable render data from leaking between requests.
lazycontroller stays focused on controller state, rendering, and typed HTTP
errors. Route definitions live in lazyroutes; request dispatch lives in
lazydispatch.
Actions
Standard actions use this signature:
func (c *Controller) Action(w http.ResponseWriter, r *http.Request) error
An action can use w and r directly, but ordinary HTML actions normally set
data and return nil:
func (c *HomeController) Index(_ http.ResponseWriter, _ *http.Request) error {
c.Set("title", "Home")
return nil
}
GoLazy renders the default controller/action view automatically when the
action has not already written a response. Call Render explicitly only when
an action needs to render a different view.
Actions can also ask GoLazy to resolve their arguments before they run:
func (c *PostsController) Show(postID int) error {
post, err := c.posts.Get(postID)
if err != nil {
return err
}
c.Set("post", post)
return nil
}
String and integer arguments consume named route parameters in route path
order. For /posts/{post_id}/comments/{comment_id}, the first string or
integer argument receives post_id and the second receives comment_id.
For richer request input, add a generator method to the controller. Generator
methods start with Gen followed by an uppercase letter and return T or
(T, error):
type PostInput struct {
Title string
}
func (c *PostsController) GenPostInput(r *http.Request) (PostInput, error) {
if err := r.ParseForm(); err != nil {
return PostInput{}, err
}
return PostInput{Title: r.Form.Get("title")}, nil
}
func (c *PostsController) Create(input PostInput) error {
post, err := c.posts.Create(input.Title)
if err != nil {
return err
}
c.Set("post", post)
return nil
}
Generators can depend on standard arguments and on other generated types. Generated values are cached for the current request, so one type is generated only once even when multiple arguments need it. Generator errors use the same controller error handling path as action errors.
View data
Set adds a value to the render data:
c.Set("posts", c.posts.List())
The value is available to both the controller view and its layout. Template data is escaped by default.
Layouts
Controllers use the app layout by default. Select another embedded layout
before returning:
c.SetLayout("admin")
return nil
This resolves layouts/admin.html.tpl.
HTTP errors
Return lazycontroller.Error when an expected failure needs a specific status:
return lazycontroller.Error(
http.StatusNotFound,
fmt.Errorf("post %q not found", slug),
)
Unexpected errors become 500 Internal Server Error. The response contains the
standard status text, while the wrapped error remains available to callers and
future logging infrastructure.
Controller design
Keep actions short:
- Read route values and request input.
- Call services.
- Set view data.
- Return
nilfor the default view, render explicitly when needed, or return an error.
Move reusable application work into services rather than growing controller methods into business-logic containers.