Controllers

Introduction

Instead of defining all of your request handling logic in a single routes.go file, you may wish to organize this behavior using controller structures. Controller structs can group related HTTP request handling logic into a single object. Controllers are stored in the controllers package located in the app/controllers/ directory.

Basic Controllers

Below is an example of a very simple home controller struct located at /app/controllers/home.go. All Gophersaurus controllers are simply structs. We recommend the pattern of declaring anonymous public structs:

package controllers

import "github.com/gophersaurus/gf.v1/http"

// Home is a controller.
var Home = struct {
  Welcome func(resp http.Responder, req *http.Request)
}{

  // Welcome is an action method for a GET request.
  Welcome: func(resp http.Responder, req *http.Request) {
    resp.Write(req, "Welcome fellow gopher.")
  },
}

We can route to the controller action like so in /app/routes.go:

package app

import (
  c "github.com/gophersaurus/project/app/controllers"
  "github.com/gophersaurus/gf.v1/router"
)

func register(r router.Router) {
  r.GET("/welcome", c.Home.Welcome)
}

Now, when a request matches the specified route URI /welcome, the Welcome method attached to the Home controller will be executed. Of course, the route parameters will also be passed to the method via the http.Request argument.

Controllers & Namespaces

It is helpful to note that the namespace for the controllers package was c in the example above. The line import c "github.com/gophersaurus/project/app/controllers" allows us to reference the controller package without typing the full word “controller”. This is a useful convention when specifying multiple routes so that controllers.Home.Welcome becomes c.Home.Welcome.

Below is an example with the shorter namespace:

func register(r router.Router) {
  r.GET("/", c.Home.Index)
  r.GET("/weather/:city", c.Weather.Show)
  r.GET("/stocks/:exchange/:stock", c.Stocks.Show)
  r.GET("/v2/stocks/:exchange/:stock", c.StocksV2.Show)
}

Controller Middleware

Middleware has been designed to bridge the router and controllers packages. Middleware is defined in the middleware package located in the /app/middleware directory. The Middleware type represents a function that takes a Handler type and returns a Handler type. This taking and returns of a Handler type creates a chain of Middleware.

type Middleware func(http.Handler) http.Handler

Typically Middleware is not attached to a particular route, instead it is assigned to a subrouter via the Router.Subrouter method. This pattern provides a powerful way to secure and version different routes based on different controller’s needs. Below is an example:

func register(r router.Router) {

  // this subrouter has URI paths prepended with "/v1" for versioning
  v1 := r.Subrouter("/v1")

  // admin resources
  admin := v1.Subrouter("/admin").Middleware(
    m.SessionAdmin,
    m.SessionRefresh,
  )
}

The example above defines a v1 subrouter. v1 inherits all Middleware (such as API key verification) from the original router. admin is a subrouter based upon v1. The Middleware method allows us to attach middleware only to admin routes.

RESTful Resource Controllers

Resource controllers make it painless to build RESTful controllers around resources. For example, you may wish to create a controller that handles HTTP requests regarding “photos” stored by your application. The photos controller file would exist at /app/controllers/photos.go. The controller will contain a method for each of the available resource operations.

Verb Path Action Route Method
GET /photo index photo.Index
POST /photo store photo.Store
GET /photo/:id show photo.Show
PUT /photo/:id update photo.Update
PATCH /photo/:id apply photo.Apply
DELETE /photo/:id destroy photo.Destroy

If a resource controller contains a method for each action method above it will satisfy the Resourcer interface.

type Resourcer interface {
  // action methods
  Index(resp http.Responder, req *http.Request)
  Show(resp http.Responder, req *http.Request)
  Store(resp http.Responder, req *http.Request)
  Apply(resp http.Responder, req *http.Request)
  Update(resp http.Responder, req *http.Request)
  Destroy(resp http.Responder, req *http.Request)
}

Next, you may register a resourceful route to the controller:

func register(r router.Router) {
  r.Resource("/photos", "id", c.Photos)
}