Skip to content

Latest commit

 

History

History
194 lines (146 loc) · 5.38 KB

File metadata and controls

194 lines (146 loc) · 5.38 KB

Controllers

Controllers handle HTTP requests and return responses. Catuaba generates two types: HTML handlers (for scaffold) and JSON API controllers (for api).

HTML handlers (scaffold)

Generated by catuaba g scaffold, these live in app/controllers/{resource}/ and render Templ views.

catuaba g scaffold Post title:string body:text published:boolean

Creates 7 handler files in app/controllers/posts/:

File Route Purpose
index.go GET /posts List with pagination
new.go GET /posts/new Render new form
create.go POST /posts Process form, create record
show.go GET /posts/:id Show detail view
edit.go GET /posts/:id/edit Render edit form
update.go POST /posts/:id Process form, update record
delete.go POST /posts/:id/delete Delete record

Handler anatomy

Each handler is a standalone function in its own file:

package posts

import (
    "net/http"
    "myapp/app/models"
    views "myapp/app/views/posts"
    "myapp/database"
    "myapp/middleware"
    "github.com/gin-gonic/gin"
)

// Show handles GET /posts/:id
func Show(ctx *gin.Context) {
    var post models.Post

    if err := database.DB.First(&post, ctx.Param("id")).Error; err != nil {
        ctx.String(http.StatusNotFound, "Record not found")
        return
    }

    flashMsg, flashType := middleware.GetFlash(ctx)
    ctx.Writer.Header().Set("Content-Type", "text/html; charset=utf-8")
    views.Show(post, flashMsg, flashType).Render(ctx.Request.Context(), ctx.Writer)
}

Key patterns:

  • Database: use database.DB (GORM)
  • URL params: ctx.Param("id")
  • Query params: ctx.DefaultQuery("page", "1")
  • Form binding: ctx.ShouldBind(&params) with a CreateParams/UpdateParams struct
  • Render view: views.Show(data, ...).Render(ctx.Request.Context(), ctx.Writer)
  • Flash messages: middleware.SetFlash(ctx, "success", "Post created!")
  • Redirect: ctx.Redirect(http.StatusSeeOther, "/posts")

Form params

Create and Update handlers use typed param structs with form binding tags:

type CreateParams struct {
    Title     string `form:"title"`
    Body      string `form:"body"`
    Published bool   `form:"published"`
}

The update handler assigns fields explicitly and uses database.DB.Save() (not .Updates()) to correctly save zero-values like false for booleans.

JSON API controllers

Generated by catuaba g api, these live in app/controllers/api/{resource}/ and return JSON.

catuaba g api Order total:float status:string

Creates 5 controller files + tests in app/controllers/api/orders/:

File Route Purpose
index.go GET /api/orders Paginated list (JSON)
create.go POST /api/orders Create (JSON body)
show.go GET /api/orders/:id Single record (JSON)
update.go PUT/PATCH /api/orders/:id Update (JSON body)
delete.go DELETE /api/orders/:id Delete

API handler anatomy

package orders

import (
    "net/http"
    "myapp/app/models"
    "myapp/database"
    "github.com/gin-gonic/gin"
)

type CreateParams struct {
    Total  float64 `json:"total" binding:"required"`
    Status string  `json:"status" binding:"required"`
}

func Create(ctx *gin.Context) {
    var params CreateParams
    if err := ctx.ShouldBindJSON(&params); err != nil {
        ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    order := &models.Order{
        Total:  params.Total,
        Status: params.Status,
    }

    if err := database.DB.Create(order).Error; err != nil {
        ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }

    ctx.JSON(http.StatusCreated, order)
}

Key differences from HTML handlers:

  • Uses ctx.ShouldBindJSON() (not ShouldBind)
  • JSON binding tags: `json:"total" binding:"required"`
  • Boolean fields skip binding:"required" (since false is zero-value)
  • Returns ctx.JSON() instead of rendering views
  • API routes are under /api/ prefix (CSRF middleware is skipped)

Generated tests

Each API controller comes with a test file:

func TestCreate(t *testing.T) {
    // Sets up test router, sends POST request, asserts 201 status
}

Standalone controller

For custom pages that don't follow CRUD patterns:

catuaba g controller Auth login logout register

Creates app/controllers/auth.go with stub handlers for each method.

Routes

All routes are auto-injected into config/routes.go:

func SetupRoutes(routes *gin.Engine) {
    routes.GET("/", controllers.Home)

    // Post routes (scaffold)
    routes.GET("/posts", posts.Index)
    routes.GET("/posts/new", posts.New)
    routes.POST("/posts", posts.Create)
    routes.GET("/posts/:id", posts.Show)
    routes.GET("/posts/:id/edit", posts.Edit)
    routes.POST("/posts/:id", posts.Update)
    routes.POST("/posts/:id/delete", posts.Delete)

    // Order API routes
    routes.GET("/api/orders", api_orders.Index)
    routes.POST("/api/orders", api_orders.Create)
    // ...

    // [catuaba:routes] — generators inject new routes above this line
}

List all routes: catuaba routes

Learn more