I'm trying to use chi middlewares to validate incremental user permissions. Without any prior experience with chi, I figured a logical approach would be:
api := chi.NewRouter()
// routes accessible to anyone
api.Get("/blob/:blobKey", BlobGET)
api.Post("/blob", BlobPOST)
api.Post("/login", UserLoginPOST)
api.Post("/forgot", UserForgotPassPOST)
// routes which require a user
api.Use(UserCtx)
api.Post("/someResource", ResourcePOST)
api.Get("/someResource", ResourceGET)
// routes for admins
api.Use(UserAdminCtx)
api.Get("/user", UserListGET)
api.Post("/user", UserCreatePOST)
This results in panic: all middlewares must be defined before routes on a mux.
Wouldn't it be a nice use case to support? The middleware stack could be copied after at least one route has been defined in the group and a new .Use is encountered.
And am I correct in assuming the current alternative is using groups resulting in quite the amount of indentation (in practice I'm having more like 5 user levels).
@EmielM yes, you should use either groups or sub-routers.
Right, thanks for the quick reply. To prevent a lot of indentation I could repeat adding the middlewares:
r.Group(function(r chi.Router) {
r.Use(Level0Ctx);
r.Post("/level0", Level0POST);
});
r.Group(function(r chi.Router) {
r.Use(Level0Ctx);
r.Use(Level1Ctx);
r.Post("/level1", Level1POST);
});
r.Group(function(r chi.Router) {
r.Use(Level0Ctx);
r.Use(Level1Ctx);
r.Use(Level2Ctx);
r.Post("/level2", Level2POST);
});
etc.
You can even pass multiple middlewares to r.Use().
r.Group(func(r chi.Router) {
r.Use(Level0Ctx)
r.Post("/level0", Level0POST)
})
r.Group(func(r chi.Router) {
r.Use(Level0Ctx, Level1Ctx)
r.Post("/level1", Level1POST)
})
r.Group(func(r chi.Router) {
r.Use(Level0Ctx, Level1Ctx, Level2Ctx)
r.Post("/level2", Level2POST)
})
thanks for the help @VojtechVitek
Just out of interest, why is the decision made to not allow interleaving definitions of middlewares and routes? It's a nice to have thing in my opinion.
@jutkko it'd be confusing.
r.Use(Mw1)
r.Get("/", Handler1)
r.Use(Mw2)
..now, is the Mw2 middleware applied to Handler1 or not?
We prefer being explicit.
Ok that makes sense. Thanks for the quick response.
I have experienced that it is very often that you incrementally add middlewares, and being explicit is fine, but adding a bunch of needless sugar in the form of .Group wrapping [s]feels stupid[/s] results in less readable code imho.
Since this ticket was opened, I have switched for my projects to a custom (internal) router that allows this approach, but uses a different method name to make it more clear:
r.Get("/login", Login)
r.Push(WithUser)
r.Get("/x", HandleX)
r.Push(WithPlayer)
r.Get("/player/:playerID/details", x)
r.Get("/player/:playerID/info", x)
r.Pop()
r.Push(WithAdmin)
r.Get("/stats", HandleStats)
Cheers
@EmielM you could use chi's With() method where Group() doesn't look readable:
r.Get("/login", Login)
r.With(WithUser).Get("/x", HandleX)
r.Group(func(r chi.Router) {
r.Use(WithPlayer)
r.Get("/player/:playerID/details", x)
r.Get("/player/:playerID/info", x)
})
r.With(WithAdmin).Get("/stats", HandleStats)
Most helpful comment
@jutkko it'd be confusing.
..now, is the
Mw2middleware applied toHandler1or not?We prefer being explicit.