-
Notifications
You must be signed in to change notification settings - Fork 0
Creating routes
This page suggests how to structure your HTTP server routes to make use of generic CRUD functions with database store packages and save yourself a ton of boilerplate. Routing is done using gorilla/mux.
Start with an application struct to hold properties which are common to the HTTP server app, the CLI app, and all other apps, such as logging and database connection pool. This is the example from the Northwind sample application:
type Application struct {
Config *nw.Config
InfoLog *slog.Logger
ErrorLog *slog.Logger
Db *pgxpool.Pool
Validate *validator.Validate
}Now make a struct specifically for the HTTP server, to which your routes will be attached. The Application struct above is embedded:
type httpServerApplication struct {
*cmd.Application
GetOptions lys.GetOptions
PostOptions lys.PostOptions
}Make a method on the httpServerApplication struct for each database schema. This contains all routes for that schema. For example, for Northwind, the abridged version of the method for the "core" schema is:
func (srvApp *httpServerApplication) coreRoutes(apiEnv lys.Env) lys.RouteAdderFunc {
return func(r *mux.Router) *mux.Router {
endpoint := "/categories"
categoryStore := corecategory.Store{Db: srvApp.Db}
r.HandleFunc(endpoint, lys.Get[corecategory.Model](apiEnv, categoryStore)).Methods("GET")
r.HandleFunc(endpoint+"/{id}", lys.GetById[corecategory.Model](apiEnv, categoryStore)).Methods("GET")
r.HandleFunc(endpoint, lys.Post[corecategory.Input, corecategory.Model](apiEnv, categoryStore)).Methods("POST")
r.HandleFunc(endpoint+"/{id}", lys.Put[corecategory.Input](apiEnv, categoryStore)).Methods("PUT")
r.HandleFunc(endpoint+"/{id}", lys.Patch(apiEnv, categoryStore)).Methods("PATCH")
r.HandleFunc(endpoint+"/{id}", lys.Delete(apiEnv, categoryStore)).Methods("DELETE")
return r
}
}Make a method on the httpServerApplication struct which defines the URL prefix and the method for each schema. For example, if there are methods for "common" and "core" schemas:
func (srvApp *httpServerApplication) getSubRoutes(apiEnv lys.Env) (subRoutes []lys.SubRoute) {
subRoutes = append(subRoutes, lys.SubRoute{Url: "/common", RouteAdder: srvApp.commonRoutes(apiEnv)})
subRoutes = append(subRoutes, lys.SubRoute{Url: "/core", RouteAdder: srvApp.coreRoutes(apiEnv)})
return subRoutes
}Make a method on the httpServerApplication struct which returns the main application router, something like this:
func (srvApp *httpServerApplication) getRouter() http.Handler {
// define Env struct needed for generic route handlers
apiEnv := lys.Env{
ErrorLog: srvApp.ErrorLog,
Validate: srvApp.Validate,
GetOptions: srvApp.GetOptions,
PostOptions: srvApp.PostOptions,
}
r := mux.NewRouter()
// public routes
r.HandleFunc("/", lys.Message("Welcome to the "+srvApp.Config.General.AppName+" API. Please log in.")).Methods("GET")
// put all routes requiring auth behind "/a" for authed
authedR := r.PathPrefix("/a").Subrouter()
// add subroutes into main router
for _, subRoute := range srvApp.getSubRoutes(apiEnv) {
subRouter := authedR.PathPrefix(subRoute.Url).Subrouter()
_ = subRoute.RouteAdder(subRouter)
}
return r
}Since we are putting routes requiring authentication behind the prefix "/a", and using a further prefix per schema, the final URL for the core.category table endpoint is: "host:port/a/core/categories".
In our HTTP server main function, we instantiate Application, httpServerApplication, and attach the main route function to a new http.Server instance:
func main() {
// create non-specific app
app := &cmd.Application{
Config: &conf,
InfoLog: slog.New(slog.NewTextHandler(os.Stdout, nil)),
ErrorLog: slog.New(slog.NewTextHandler(os.Stderr, nil)),
Validate: validator.New(validator.WithRequiredStructEnabled()),
}
// create http server app
srvApp := &httpServerApplication{
Application: app,
GetOptions: lys.FillGetOptions(lys.GetOptions{}), // use defaults
PostOptions: lys.FillPostOptions(lys.PostOptions{}), // use defaults
}
// create HTTP server using srvApp's routes and handlers
srv := &http.Server{
Addr: ":" + srvApp.Config.API.Port,
Handler: srvApp.getRouter(),
}
// start server..
}