I’m writing an API with some basic CRUD operations, and I’d like to be able to have my “photos” resource as a sub-resource of my “events” (i.e. requests are made against api/events/photos
). I also have a dedicated /api/photos
endpoint for whatever else, but I really want to be able to use the URL parameters for the event itself in getting the photos as this seems more sensible to me. However, after some finagling I’ve gotten subrouting to work acceptably, but the r.PathValue("eventId")
within my subrouting handler simply returns an empty string.
In one file, I sew all of my routers together, initializing each http.ServeMux
with the routes from that service.
<code>rootRouter := http.NewServeMux()
// supply routes from service-specific route additions
photoStore := photos.NewStore(s.db)
photoHandler := photos.NewHandler(photoStore)
photoRouter := http.NewServeMux()
photoHandler.RegisterRoutes(photoRouter)
rootRouter.Handle("/photos/", http.StripPrefix("/photos", photoRouter))
photoSubRouter := http.NewServeMux()
photoHandler.RegisterSubRoutes(photoSubRouter)
eventStore := events.NewStore(s.db)
eventHandler := events.NewHandler(eventStore)
eventRouter := http.NewServeMux()
eventHandler.RegisterRoutes(eventRouter, photoSubRouter)
rootRouter.Handle("/events/", http.StripPrefix("/events", eventRouter))
<code>rootRouter := http.NewServeMux()
// supply routes from service-specific route additions
// - Photo Routing -
photoStore := photos.NewStore(s.db)
photoHandler := photos.NewHandler(photoStore)
photoRouter := http.NewServeMux()
photoHandler.RegisterRoutes(photoRouter)
rootRouter.Handle("/photos/", http.StripPrefix("/photos", photoRouter))
// sub router
photoSubRouter := http.NewServeMux()
photoHandler.RegisterSubRoutes(photoSubRouter)
// - Event Routing -
eventStore := events.NewStore(s.db)
eventHandler := events.NewHandler(eventStore)
eventRouter := http.NewServeMux()
eventHandler.RegisterRoutes(eventRouter, photoSubRouter)
rootRouter.Handle("/events/", http.StripPrefix("/events", eventRouter))
</code>
rootRouter := http.NewServeMux()
// supply routes from service-specific route additions
// - Photo Routing -
photoStore := photos.NewStore(s.db)
photoHandler := photos.NewHandler(photoStore)
photoRouter := http.NewServeMux()
photoHandler.RegisterRoutes(photoRouter)
rootRouter.Handle("/photos/", http.StripPrefix("/photos", photoRouter))
// sub router
photoSubRouter := http.NewServeMux()
photoHandler.RegisterSubRoutes(photoSubRouter)
// - Event Routing -
eventStore := events.NewStore(s.db)
eventHandler := events.NewHandler(eventStore)
eventRouter := http.NewServeMux()
eventHandler.RegisterRoutes(eventRouter, photoSubRouter)
rootRouter.Handle("/events/", http.StripPrefix("/events", eventRouter))
For my event routing, I pass in a primary router and a subrouter for handling the the subresource specifically, just so all of my photo endpoint logic can remain within that service.
func (h *Handler) RegisterRoutes(router *http.ServeMux, subRouter *http.ServeMux) {
router.HandleFunc("GET /{$}", h.handleEventsGet)
router.HandleFunc("GET /{eventId}", h.handleEventGetByID)
router.Handle("/{eventId}/photos/", subRouter)
<code>
func (h *Handler) RegisterRoutes(router *http.ServeMux, subRouter *http.ServeMux) {
router.HandleFunc("GET /{$}", h.handleEventsGet)
router.HandleFunc("GET /{eventId}", h.handleEventGetByID)
router.Handle("/{eventId}/photos/", subRouter)
</code>
func (h *Handler) RegisterRoutes(router *http.ServeMux, subRouter *http.ServeMux) {
router.HandleFunc("GET /{$}", h.handleEventsGet)
router.HandleFunc("GET /{eventId}", h.handleEventGetByID)
router.Handle("/{eventId}/photos/", subRouter)
This handler (handlePhotosGetByEventID
) is just a demonstration, but from testing it I do indeed get a URL path that includes the eventId. However, the call to r.PathValue("eventId")
simply returns an empty string. I’ve considered doing some sort of string split thing and manually determining what characters should be the eventId but that feels less secure, so I wanted to see if this was just a limitation of the routers themselves, as opposed to my incompetence.
func (h *Handler) RegisterSubRoutes(router *http.ServeMux) {
router.HandleFunc("GET /", h.handlePhotosGetByEventID)
router.HandleFunc("GET /{photoId}", h.handlePhotoGetByID)
func (h *Handler) handlePhotosGetByEventID(w http.ResponseWriter, r *http.Request) {
log.Println("Hello! You've requested the photos associated with this event")
id, err := strconv.Atoi(r.PathValue("eventId"))
// malformed id unable to be converted
utils.WriteError(w, http.StatusBadRequest, err)
log.Println("Event ID: ", id, " But also I'm photos")
if err := utils.Encode(w, 200, map[string]string{"event": strconv.Itoa(id), "photos": "many"}); err != nil {
utils.WriteError(w, http.StatusInternalServerError, err)
<code>
func (h *Handler) RegisterSubRoutes(router *http.ServeMux) {
router.HandleFunc("GET /", h.handlePhotosGetByEventID)
router.HandleFunc("GET /{photoId}", h.handlePhotoGetByID)
}
func (h *Handler) handlePhotosGetByEventID(w http.ResponseWriter, r *http.Request) {
log.Println("Hello! You've requested the photos associated with this event")
log.Println(r.URL.Path)
id, err := strconv.Atoi(r.PathValue("eventId"))
// malformed id unable to be converted
if err != nil {
utils.WriteError(w, http.StatusBadRequest, err)
return
}
log.Println("Event ID: ", id, " But also I'm photos")
if err := utils.Encode(w, 200, map[string]string{"event": strconv.Itoa(id), "photos": "many"}); err != nil {
utils.WriteError(w, http.StatusInternalServerError, err)
return
}
}
</code>
func (h *Handler) RegisterSubRoutes(router *http.ServeMux) {
router.HandleFunc("GET /", h.handlePhotosGetByEventID)
router.HandleFunc("GET /{photoId}", h.handlePhotoGetByID)
}
func (h *Handler) handlePhotosGetByEventID(w http.ResponseWriter, r *http.Request) {
log.Println("Hello! You've requested the photos associated with this event")
log.Println(r.URL.Path)
id, err := strconv.Atoi(r.PathValue("eventId"))
// malformed id unable to be converted
if err != nil {
utils.WriteError(w, http.StatusBadRequest, err)
return
}
log.Println("Event ID: ", id, " But also I'm photos")
if err := utils.Encode(w, 200, map[string]string{"event": strconv.Itoa(id), "photos": "many"}); err != nil {
utils.WriteError(w, http.StatusInternalServerError, err)
return
}
}
What am I missing here?