A single server structure that usually ends up looking something like this:
type server struct {
db *someDatabase
router *someRouter
email EmailSender
}
Shared dependencies are fields of the structure
package app
func (s *server) routes() {
s.router.HandleFunc("/api/", s.handleAPI())
s.router.HandleFunc("/about", s.handleAbout())
s.router.HandleFunc("/", s.handleIndex())
}
HTTP handlers hang off the server:
func (s *server) handleSomething() http.HandlerFunc { ... }
Handler functions don’t actually handle the requests, they return a function that does.
func (s *server) handleSomething() http.HandlerFunc {
thing := prepareThing()
return func(w http.ResponseWriter, r *http.Request) {
// use thing
}
}
func (s *server) adminOnly(h http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if !currentUser(r).IsAdmin {
http.NotFound(w, r)
return
}
h(w, r)
}
}
package app
func (s *server) routes() {
s.router.HandleFunc("/api/", s.handleAPI())
s.router.HandleFunc("/about", s.handleAbout())
s.router.HandleFunc("/", s.handleIndex())
s.router.HandleFunc("/admin", s.adminOnly(s.handleAdminIndex))
}
If an endpoint has its own request and response types, usually they’re only useful for that particular handler.
func (s *server) handleSomething() http.HandlerFunc {
type request struct {
Name string
}
type response struct {
Greeting string `json:"greeting"`
}
return func(w http.ResponseWriter, r *http.Request) {
...
}
}
Our server type is very testable.
func TestHandleAbout(t *testing.T) {
is := is.New(t)
srv := server{
db: mockDatabase,
email: mockEmailSender,
}
srv.routes()
req, err := http.NewRequest("GET", "/about", nil)
is.NoErr(err)
w := httptest.NewRecorder()
srv.ServeHTTP(w, req)
is.Equal(w.StatusCode, http.StatusOK)
}