Chi: Define middlewares between routes

Created on 4 Jan 2017  路  9Comments  路  Source: go-chi/chi

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).

Most helpful comment

@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.

All 9 comments

@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)
Was this page helpful?
0 / 5 - 0 ratings

Related issues

rickb777 picture rickb777  路  12Comments

makhov picture makhov  路  4Comments

nhooyr picture nhooyr  路  12Comments

jsadwith picture jsadwith  路  6Comments

rocanion picture rocanion  路  4Comments