Routing
/internal/server/routes.go
We recommend that Gofs apps keep routing code for the entire app in /internal/server/routes.go
. This makes it easy to manage all routing from one place. You can use multiple files or create a module for the routes if routing gets very complex, however it may be worth simplifying your routing design if this is starting to happen.
The Gofs default template ships with routing and handlers for static assets in the Routes
method, shown below.
func (s *Server) Routes() {
// filserver route for assets
assetMux := http.NewServeMux()
assetMux.Handle("GET /{path...}", http.StripPrefix("/assets/", handlers.NewHashedAssets(assets.FS)))
s.r.Handle("GET /assets/{path...}", s.assetsMiddlewares(assetMux))
...
// you can start adding your routes here
}
Uniformity
We recommend page routes follow the structure of pages.
func (s *Server) Routes() {
...
routesMux := http.NewServeMux()
routesMux.Handle("GET /{$}", page.Index())
routesMux.Handle("GET /mysection1/mypage1", page.MySection1MyPage1())
routesMux.Handle("GET /mysection1/mypage2", page.MySection1MyPage2())
routesMux.Handle("GET /mysection2/mypage1", page.MySection2MyPage1())
s.r.Handle("/", s.routeMiddlewares(routesMux()))
...
}
HTTP verbs
Remember that Gofs pages are server-side rendered and call the backend directly instead of through an api, see here. GET http calls should return a page, or HTML fragment that HTMX will swap into the page. POST, PUT, DELETE http calls will perform their expected function and also return either a page or HTML fragment. We recommend making the action of the http call clear from the route. For example
routesMux.Handle("POST /myobject/create", page.CreateMyObject())
routesMux.Handle("PUT /myobject/update", page.UpdateMyObject())
routesMux.Handle("DELETE /myobject/delete", page.DeleteMyObject())
Statelessness
Gofs endpoints should be stateless and should use path variables to indicate which elements they are referring to. For example
routesMux.Handle("GET /myobject/{id}/view", page.ViewMyObject())
routesMux.Handle("PUT /myobject/{id}/update", page.UpdateMyObject())
routesMux.Handle("DELETE /myobject/{id}/delete", page.DeleteMyObject())
Example
Lets looks at the mytodo app example we started earlier. There is only one page which is the index page, and there are two action endpoints for adding a a todo list and adding an item to a todo list. A complete example of the routing file /internal/server/routes.go
for mytodo is shown below.
package server
import (
"net/http"
"github.com/myorg/mytodo/internal/server/assets"
"github.com/myorg/mytodo/internal/server/handlers"
"github.com/myorg/mytodo/internal/server/handlers/page"
"github.com/myorg/mytodo/internal/server/logging"
"github.com/myorg/mytodo/internal/server/telemetry/metrics"
)
func (s *Server) Routes() {
// fileserver route for assets
assetMux := http.NewServeMux()
assetMux.Handle("GET /{path...}", http.StripPrefix("/assets/", handlers.NewHashedAssets(assets.FS)))
s.r.Handle("GET /assets/{path...}", s.assetsMiddlewares(assetMux))
// handlers for normal routes with all general middleware
routesMux := http.NewServeMux()
routesMux.Handle("GET /{$}", page.Index(s.db))
routesMux.Handle("POST /list/create", page.CreateList(s.db))
routesMux.Handle("POST /list/{id}/add-item", page.AddItem(s.db))
s.r.Handle("/", s.routeMiddlewares(routesMux))
s.srv.Handler = metrics.Expose(s.r)
}