chi icon indicating copy to clipboard operation
chi copied to clipboard

URL params are not available inside middleware on the root router

Open dennisvanderweide opened this issue 1 year ago • 6 comments

When I create a middleware to log the id URL parameter, which exists in the route I request, the value is always empty. I do get a value inside the handler itself

r := chi.NewRouter()

r.Use(func(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		id := chi.URLParam(r, "id")
		log.Println(id)
		h.ServeHTTP(w, r)
	})
})

r.Get("/test/{id}", func(w http.ResponseWriter, r *http.Request) {
	id := router.Param(r, "id").String()
	io.WriteString(w, id)
})

When I move this code to a Group, the URL param will become available inside the middleware.

r := chi.NewRouter()

r.Group(func(r chi.Router) {
	r.Use(func(h http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			id := chi.URLParam(r, "id")
			log.Println(id)
			h.ServeHTTP(w, r)
		})
	})

	r.Get("/test/{id}", func(w http.ResponseWriter, r *http.Request) {
		id := router.Param(r, "id").String()
		io.WriteString(w, id)
	})
})

I think this behavior is quite strange and prone to errors

Tested with the latest version (5.0.11)

dennisvanderweide avatar Jan 04 '24 14:01 dennisvanderweide

I have the same issue

erwinschaap avatar Jan 19 '24 14:01 erwinschaap

This behavior is due to the way the chi router handles middleware and route patterns. When you register a middleware using r.Use() at the root level, the middleware is applied to all routes, including routes that do not have any URL parameters. As a result, when the middleware tries to extract the "id" parameter using chi.URLParam(r, "id"), it doesn't find it because the current route doesn't have an "id" parameter.

sakthi-lucia0567 avatar Apr 13 '24 18:04 sakthi-lucia0567

The behavior you describe would be what I would expect to be the behavior, but the problem is that when I execute an HTTP request for a route which does have an id parameter, it won't be able to find the parameter even though the route does contain a parameter.

dennisvanderweide avatar Apr 17 '24 08:04 dennisvanderweide

mx.URLParams gets populated only at the routeHTTP function which will not get the id param if your middleware is at the top level (* route). Try this instead:

v1Router := chi.NewRouter()
v1Router.Route("/test/{id}", func(r chi.Router) {
	r.Use(Middleware)
	r.Get("/", handler.getHandler)
	r.Patch("/", handler.updateHandler)
	r.Delete("/", handler.deleteHandler)
})

ning-kang avatar May 26 '24 09:05 ning-kang

Thanks! That works but is super optimal. I need to repeat the middleware call for every endpoint group.

Example:

v1Router := chi.NewRouter()
v1Router.Route("/test/{id}", func(r chi.Router) {
	r.Use(Middleware)
	r.Get("/", handler.getHandler)
	r.Patch("/", handler.updateHandler)
	r.Delete("/", handler.deleteHandler)
})

v1Router := chi.NewRouter()
v1Router.Route("/another-test/{id}", func(r chi.Router) {
	r.Use(Middleware)
	r.Get("/", handler.getHandler)
	r.Patch("/", handler.updateHandler)
	r.Delete("/", handler.deleteHandler)
})

That is a problem because I want to ensure that the middleware (authZ) is called on every request. Do I miss anything here or is there a better way to get the URL params in the middleware (I need the params to make a call on the authZ).

jfcdigitalventures avatar Aug 16 '24 16:08 jfcdigitalventures