I'm experimenting with routing in Go and ran into an issue where my API always returns a 404 Not Found when I try to access an endpoint like:
https://localhost:8080/api/v1/users/login
Here’s my routing setup:
func SetupRoutes(cfg *handler.Config, version string) http.Handler {
mux := http.NewServeMux()
apiPath := "/api/v" + version + "/"
versionedRoutes := map[string]http.Handler{
"users/": SetupUserRoutes(cfg),
"workouts/": SetupWorkoutRoutes(cfg),
"sessions/": SetupSessionRoutes(cfg),
"admin/": SetupAdminRoutes(cfg),
}
// Register each versioned route with the main mux
for path, handler := range versionedRoutes {
mux.Handle(apiPath+path, http.StripPrefix(apiPath+path, handler))
}
fmt.Printf("%+v\n", mux)
return mux
}
And my user routes:
func SetupUserRoutes(cfg *handler.Config) http.Handler {
mux := http.NewServeMux()
userRoutes := map[string]http.HandlerFunc{
"/register": cfg.RegisterUser,
"/login": cfg.LoginUser,
"/logout": cfg.LogoutUser,
"/edit": cfg.EditUser,
"/revoke": cfg.PostRevoke,
"/refresh": cfg.PostRefresh,
"/": cfg.ViewUser,
}
for path, handler := range userRoutes {
mux.HandleFunc(path, handler)
}
return mux
}
Issue:
- The API works when I serve it on port 8080, but it returns
404 Not Found
when I try accessing it via the versioned API path (/api/v1/users/login
). - I'm not sure if routing with
http.StripPrefix
in this way is correct.
Questions:
- Why is my API returning a 404 when using
/api/v1/users/login
? - Is it possible to route like this using
http.NewServeMux()
? - What is the correct way to structure these routes so that they work under
/api/v1/
?
Would appreciate any insights. Thanks!
I'm experimenting with routing in Go and ran into an issue where my API always returns a 404 Not Found when I try to access an endpoint like:
https://localhost:8080/api/v1/users/login
Here’s my routing setup:
func SetupRoutes(cfg *handler.Config, version string) http.Handler {
mux := http.NewServeMux()
apiPath := "/api/v" + version + "/"
versionedRoutes := map[string]http.Handler{
"users/": SetupUserRoutes(cfg),
"workouts/": SetupWorkoutRoutes(cfg),
"sessions/": SetupSessionRoutes(cfg),
"admin/": SetupAdminRoutes(cfg),
}
// Register each versioned route with the main mux
for path, handler := range versionedRoutes {
mux.Handle(apiPath+path, http.StripPrefix(apiPath+path, handler))
}
fmt.Printf("%+v\n", mux)
return mux
}
And my user routes:
func SetupUserRoutes(cfg *handler.Config) http.Handler {
mux := http.NewServeMux()
userRoutes := map[string]http.HandlerFunc{
"/register": cfg.RegisterUser,
"/login": cfg.LoginUser,
"/logout": cfg.LogoutUser,
"/edit": cfg.EditUser,
"/revoke": cfg.PostRevoke,
"/refresh": cfg.PostRefresh,
"/": cfg.ViewUser,
}
for path, handler := range userRoutes {
mux.HandleFunc(path, handler)
}
return mux
}
Issue:
- The API works when I serve it on port 8080, but it returns
404 Not Found
when I try accessing it via the versioned API path (/api/v1/users/login
). - I'm not sure if routing with
http.StripPrefix
in this way is correct.
Questions:
- Why is my API returning a 404 when using
/api/v1/users/login
? - Is it possible to route like this using
http.NewServeMux()
? - What is the correct way to structure these routes so that they work under
/api/v1/
?
Would appreciate any insights. Thanks!
Share Improve this question edited Mar 22 at 8:43 har17bar 9021 gold badge9 silver badges24 bronze badges asked Mar 6 at 2:37 CheezecakeCheezecake 214 bronze badges1 Answer
Reset to default 0With this setup, the route parsing will to like this:
mux
receives/api/v1/users/login
mux
matches that to/api/v1/
to be handled byapiMux
apiMux
receives/api/v1/users/login
apiMux
doesn't find a match
What you'll want to do is change mux.Handle(apiPath, apiMux)
to mux.Handle(apiPath, http.StripPrefix(apiPath, apiMux))
Then, the trace becomes something like this:
mux
receivesPOST /api/v1/users/login
mux
matches that to/api/v1/
to be handled byhttp.StripPrefix("/api/v1", apiMux)
apiMux
receivesPOST /users/login
apiMux
matches that to/users
to be handled byhttp.StripPrefix("/users", userMux)
userMux
receivesPOST /login
userMux
matches that toPOST /login
to be handled bycfg.LoginUser
It was probably a bit difficult to see this. Had you reduced your code to the absolute minimum needed to pre-produce the issue, it would have been obvious right away that you have two "levels" of prefixing, but only strip the prefix once.