Search documentation

Find a page in the Andurel docs.

Controllers

Controllers are the HTTP boundary. They receive Echo contexts, parse and validate input, coordinate injected models or services, and choose an Inertia, Templ, JSON, redirect, or error response.

Explicit dependencies

Model APIs are values such as models.Products, not package globals. Fx supplies them (and the Inertia renderer when needed) to the controller constructor:

1type Products struct {
2    products models.Products
3    renderer *inertia.Renderer
4}
5
6func NewProducts(products models.Products, renderer *inertia.Renderer) Products {
7    return Products{products: products, renderer: renderer}
8}

Use etx.Request().Context() when calling models and services so cancellation crosses the HTTP boundary. Do not stash collaborators on Echo context keys.

Inertia (default UI)

Generated Inertia apps inject *inertia.Renderer. One Page call covers the first HTML document and later JSON visits:

 1func (p Products) Index(etx *echo.Context) error {
 2    products, err := p.products.All(etx.Request().Context())
 3    if err != nil {
 4        return err
 5    }
 6
 7    return p.renderer.Page(
 8        etx,
 9        "Products/Index",
10        inertia.FromStruct(ProductIndexProps{Items: toProductData(products)}),
11    ).Render()
12}

Validation failures stay on the same visit with protected errors:

1if validationErrors, ok := validation.As(err); ok {
2    return p.renderer.Page(etx, "Products/Edit", props).
3        ValidationErrors(validationErrors.ToMap()).
4        Render()
5}

Redirect helpers:

Method Use
Redirect(etx, location, status) Ordinary Inertia-aware redirect (POST upgrades 302 to 303)
Location(etx, location) Hard visit / full document load (login success often uses this)
1return p.renderer.Redirect(etx, routes.ProductIndex.URL(), http.StatusSeeOther)

Map domain rows to payload structs before Page. Do not pass models.Product or narsilc types into props. See Props and Shared Data and Redirects.

Templ and JSON

Templ controllers skip the renderer and call hypermedia helpers:

1func (p Products) Index(etx *echo.Context) error {
2    products, err := p.products.All(etx.Request().Context())
3    if err != nil {
4        return err
5    }
6    return hypermedia.RenderPage(etx, views.ProductsIndex(products))
7}

API / --api controllers return Echo JSON and keep application DTOs at the boundary:

1return etx.JSON(http.StatusOK, ProductIndexProps{Items: items})
Response Typical call Contract
Inertia renderer.Page(...).Render() Page name + JSON props / redirects
Templ hypermedia.RenderPage / fragments Typed Templ components
JSON etx.JSON Application DTOs only

Generate controllers

1andurel generate controller Product
2andurel generate controller Product index show
3andurel generate controller Dashboard overview
4andurel generate controller v1/User --api

Use --model-name when a controller is backed by a differently named model, or --api for JSON handlers. Otherwise generation follows the UI recorded in andurel.toml (Inertia pages by default, or Templ).