How to use a middleware for a specific route, among other ones?

Issue

My simplified routing is similar to

r.Route("/api/v1", func(r chi.Router) {
        r.Route("/case", func(r chi.Router) {
            // generic case - for everyone
            r.Get("/{uuid}", caseGetByUuid)
            r.Put("/", casePut)
            // all cases only available to admins
            // r.Use(ensureAdminUser)  // ← this is the place I have an error
            r.Get("/", caseGetAll)
        }
        // admin endpoint
        r.Route("/admin", func(r chi.Router) {
            // ensure that the user is an admin
            r.Use(ensureAdminUser)
            r.Route("/user", func(r chi.Router) {
                r.Route("/token", func(r chi.Router) { // /admin/user/token
                    r.Get("/", userTokenGetAll)
                    r.Put("/", userTokenCreate)
                    r.Delete("/", userTokenDelete)
                })
            })
        })
    })

The second route (/admin) is restricted by a middleware that will break the chain if specific constraints are not met. The middleware is placed ahead of all the routes.

I wanted to do similar filtering in the first route (/case), but only for one route (out of the three). Uncommenting r.Use(ensureAdminUser) leads to

panic: chi: all middlewares must be defined before routes on a mux

I cannot have two routes for /case either.

Is there a way to keep the route /case and restrict one of the methods for the root call?

If not I will create an alternative route for the restricted case.

Solution

You can wrap the middleware and subsequent routes in their own group (emphasis mine):

// Group adds a new inline-Router along the current routing
// path, with a fresh middleware stack for the inline-Route.
Group(fn func(r Router)) Router

    r.Route("/api/v1", func(r chi.Router) {
        r.Route("/case", func(r chi.Router) {

            // generic case - for everyone
            r.Get("/{uuid}", caseGetByUuid)
            r.Put("/", casePut)

            // all cases only available to admins
            r.Group(func(r chi.Router) {
                r.Use(ensureAdminUser)
                r.Get("/", caseGetAll)
            })
        }
    })

It will work also with a sub-router with r.Route.

Another option when a middleware is applied to only one route is r.With which allows you to “inline” the middleware:

r.Route("/api/v1", func(r chi.Router) {
    r.Route("/case", func(r chi.Router) {

        // generic case - for everyone
        r.Get("/{uuid}", caseGetByUuid)
        r.Put("/", casePut)

        // all cases only available to admins
        r.With(ensureAdminUser).Get("/", caseGetAll)
    }
})

Answered By – blackgreen

Answer Checked By – Willingham (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.