Skip to content

Controllers & AutoRoute

Controllers handle HTTP requests. With AutoRoute, registering a controller is enough — routes are created automatically based on method names.

package main
import "github.com/semutdev/goigniter/system/core"
type Welcome struct {
core.Controller
}
func (w *Welcome) Index() {
w.Ctx.View("welcome", core.Map{
"Title": "Welcome",
})
}
func (w *Welcome) About() {
w.Ctx.View("about", core.Map{})
}

Key points:

  • Controllers embed core.Controller
  • Index() is the default method, mapped to the controller root path
  • Access request and response via w.Ctx
func main() {
app := core.New()
core.Register(&Welcome{})
core.Register(&Product{})
core.Register(&Dashboard{}, "admin") // with prefix
app.AutoRoute()
app.Run(":8080")
}

Routes are automatically generated from controller and method names.

MethodHTTP MethodsURL Pattern
Index()GET, POST/{controller}
Create()GET, POST/{controller}/create
Store(), Save(), Insert(), Update(), Delete(), Destroy(), Remove()POST/{controller}/{method}
Any other methodGET, POST/{controller}/{method}

Methods that change data are POST only, so they can’t be triggered by a plain link or an <img> tag. This covers the verbs above and longer names built on them, such as DeleteAll() or UpdateProfile() (but not Deleted()). Use AllowedMethods() to change it.

Restrict which HTTP methods a controller action accepts:

type Auth struct {
core.Controller
}
func (a *Auth) AllowedMethods() map[string][]string {
return map[string][]string{
"Login": {"GET"},
"Dologin": {"POST"},
"Logout": {"GET", "POST"},
}
}
func (a *Auth) Login() {
a.Ctx.View("auth/login", core.Map{"Title": "Login"})
}
func (a *Auth) Dologin() {
email := a.Ctx.FormValue("email")
password := a.Ctx.FormValue("password")
// authenticate
}

A method listed in AllowedMethods() accepts only the HTTP methods you give it. Methods not listed keep the defaults from the table above.

For routes with parameters like :id, define custom patterns:

type Product struct {
core.Controller
}
func (p *Product) Routes() map[string]string {
return map[string]string{
"Edit": "edit/:id",
"Update": "update/:id",
"Delete": "delete/:id",
"Detail": "detail/:id",
}
}

Routes starting with / are absolute paths:

func (p *Product) Routes() map[string]string {
return map[string]string{
"Detail": "/item/:id", // absolute: /item/:id
"Edit": "edit/:id", // relative: /product/edit/:id
}
}

Complete example:

type Product struct {
core.Controller
}
func (p *Product) Routes() map[string]string {
return map[string]string{
"Edit": "edit/:id",
"Update": "update/:id",
"Delete": "delete/:id",
}
}
func (p *Product) Index() {
products := getProductsFromDB()
p.Ctx.View("products/index", core.Map{"Products": products})
}
func (p *Product) Edit() {
id := p.Ctx.Param("id")
product := getProductByID(id)
p.Ctx.View("products/edit", core.Map{"Product": product})
}
func (p *Product) Update() {
id := p.Ctx.Param("id")
name := p.Ctx.FormValue("name")
updateProduct(id, name)
p.Ctx.Redirect(302, "/product")
}
func (p *Product) Delete() {
id := p.Ctx.Param("id")
deleteProduct(id)
p.Ctx.JSON(200, core.Map{"success": true})
}

Add a prefix when registering:

core.Register(&Dashboard{}, "admin")
core.Register(&User{}, "admin")

Results:

Product → /product
Dashboard → /admin/dashboard
User → /admin/user
func (p *Product) Store() {
name := p.Ctx.FormValue("name")
price := p.Ctx.FormValue("price")
page := p.Ctx.Query("page")
id := p.Ctx.Param("id")
var data map[string]any
p.Ctx.Bind(&data)
}
p.Ctx.JSON(200, core.Map{"status": "success"})
p.Ctx.View("products/index", core.Map{"products": products})
p.Ctx.String(200, "Hello World")
p.Ctx.Redirect(302, "/login")
p.Ctx.File("/path/to/file.pdf")
p.Ctx.NoContent(204)

Next: Middleware for authentication, logging, and security.