Chi: Adding middleware to specific rest methods under a route

Created on 26 Jan 2018  路  2Comments  路  Source: go-chi/chi

Is there no way to add middleware handling to a specific REST method under a route? Maybe I'm missing something but so far I have not been able to find a way to cleanly handle adding an auth middleware on POST, PUT, and DELETE, while leaving GET public.

For example setting up a router with 2 groups. One public and one private raises the error panic: chi: attempting to Mount() a handler on an existing path, '/{cid}':

type Resource struct{}

// Routes creates a REST router for the Resource
func (rs Resource) Routes() chi.Router {
    r := chi.NewRouter()

    // Protected routes
    r.Group(func(r chi.Router) {

        // Handle valid / invalid tokens
        // This can be modified using the Authenticator method in jwtauth.go
        r.Use(jwtauth.Authenticator)

        r.Post("/", rs.Post) // POST /{resource} - create a new resource and persist it
        r.Route("/{cid}", func(r chi.Router) {
            r.Put("/", rs.Put)       // PUT /{resource}/{cid} - update a single resource by cid
            r.Delete("/", rs.Delete) // DELETE /{resource}/{cid} - delete a single resource by cid
        })
    })

    // Public routes
    r.Group(func(r chi.Router) {
        r.Get("/", rs.List)
        r.Route("/{cid}", func(r chi.Router) {
            r.Get("/", rs.Get) // GET /{resource}/{cid} - read a single resource by cid
        })
    })

    return r
}

But trying to put the logic under one router and supplying middleware after GET methods raises the error: panic: chi: all middlewares must be defined before routes on a mux

func (rs Resource) Routes() chi.Router {
    r := chi.NewRouter()

    // Protected routes
    r.Group(func(r chi.Router) {
        // Public routes
        r.Get("/", rs.List)

        // Private routes
        // Handle valid / invalid tokens
        // This can be modified using the Authenticator method in auth.go
        r.Use(jtwauth.Authenticator)
        r.Post("/", rs.Post) // POST /{resource} - create a new resource and persist it

        r.Route("/{cid}", func(r chi.Router) {
            // Public routes
            r.Get("/", rs.Get) // GET /{resource}/{cid} - read a single resource by cid

            // Private routes
            r.Use(jwtauth.Authenticator)
            r.Put("/", rs.Put)       // PUT /{resource}/{cid} - update a single resource by cid
            r.Delete("/", rs.Delete) // DELETE /{resource}/{cid} - delete a single resource by cid
        })
    })

    return r

It seems that removing the panic in the Use method in chi/mux.go file solves the problem, but I am not sure if this is a real limitation of this library or I am going about it the wrong way, and maybe there's a more idiomatic way to handle what I'm trying to achieve. Thanks!

Most helpful comment

Hi @pkieltyka thanks so much for the response! This was really close. The only issue was that a GET request under the /{cid} route still returned a 401, but your example gave me the nudge in the right direction I needed to figure it out :). All I needed to change was to move the /{cid} route out of the 2nd group, but then keep the final group with the reapplied jwtauth middleware above PUT and DELETE. It seems that all middlewares cascade down to subgroups after being included in an outer group. Below is the version with my slight change. Again thanks for your help and for all your contributions to open source Go!

func (rs Resource) Routes() chi.Router {
    r := chi.NewRouter()

    // Protected routes
    r.Group(func(r chi.Router) {
        // Public routes
        r.Get("/", rs.List)

        // Private routes
        r.Group(func(r chi.Router) {
            // Handle valid / invalid tokens
            // This can be modified using the Authenticator method in jwtauth.go
            r.Use(jwtauth.Authenticator)
            r.Post("/", rs.Post) // POST /{resource} - create a new resource and persist it
        })

        r.Route("/{cid}", func(r chi.Router) {
            // Public routes
            r.Get("/", rs.Get) // GET /{resource}/{cid} - read a single resource by cid

            // Private routes
            r.Group(func(r chi.Router) {
                r.Use(jwtauth.Authenticator)
                r.Put("/", rs.Put)       // PUT /{resource}/{cid} - update a single resource by cid
                r.Delete("/", rs.Delete) // DELETE /{resource}/{cid} - delete a single resource by cid
            })
        })
    })

All 2 comments

hey @alexanderattar I modified your example above slightly, give this a try:

func (rs Resource) Routes() chi.Router {
  r := chi.NewRouter()

  // Protected routes
  r.Group(func(r chi.Router) {
    // Public routes
    r.Get("/", rs.List)
    r.Get("/{cid}", rs.Get) // GET /{resource}/{cid} - read a single resource by cid

    // Private routes
    // Handle valid / invalid tokens
    // This can be modified using the Authenticator method in auth.go
    r.Group(func(r chi.Router) {
      r.Use(jtwauth.Authenticator)
      r.Post("/", rs.Post) // POST /{resource} - create a new resource and persist it

      r.Route("/{cid}", func(r chi.Router) {
        r.Put("/", rs.Put)       // PUT /{resource}/{cid} - update a single resource by cid
        r.Delete("/", rs.Delete) // DELETE /{resource}/{cid} - delete a single resource by cid
      })
    })
  })

  return r
}

Hi @pkieltyka thanks so much for the response! This was really close. The only issue was that a GET request under the /{cid} route still returned a 401, but your example gave me the nudge in the right direction I needed to figure it out :). All I needed to change was to move the /{cid} route out of the 2nd group, but then keep the final group with the reapplied jwtauth middleware above PUT and DELETE. It seems that all middlewares cascade down to subgroups after being included in an outer group. Below is the version with my slight change. Again thanks for your help and for all your contributions to open source Go!

func (rs Resource) Routes() chi.Router {
    r := chi.NewRouter()

    // Protected routes
    r.Group(func(r chi.Router) {
        // Public routes
        r.Get("/", rs.List)

        // Private routes
        r.Group(func(r chi.Router) {
            // Handle valid / invalid tokens
            // This can be modified using the Authenticator method in jwtauth.go
            r.Use(jwtauth.Authenticator)
            r.Post("/", rs.Post) // POST /{resource} - create a new resource and persist it
        })

        r.Route("/{cid}", func(r chi.Router) {
            // Public routes
            r.Get("/", rs.Get) // GET /{resource}/{cid} - read a single resource by cid

            // Private routes
            r.Group(func(r chi.Router) {
                r.Use(jwtauth.Authenticator)
                r.Put("/", rs.Put)       // PUT /{resource}/{cid} - update a single resource by cid
                r.Delete("/", rs.Delete) // DELETE /{resource}/{cid} - delete a single resource by cid
            })
        })
    })
Was this page helpful?
0 / 5 - 0 ratings

Related issues

rickb777 picture rickb777  路  12Comments

EmielM picture EmielM  路  9Comments

netsharec picture netsharec  路  6Comments

chenjie4255 picture chenjie4255  路  11Comments

jsadwith picture jsadwith  路  6Comments