23
votes

I've been working on a Go project where gorilla/mux is used as the router.

I need to be able to have query values associated with a route, but these values should be optional. That means that I'd like to catch both /articles/123 and /articles/123?key=456 in the same handler.

To accomplish so I tried using the r.Queries method that accepts key/value pairs: router.

  Path("/articles/{id:[0-9]+}").Queries("key", "{[0-9]*?}")

but this makes only the value (456) optional, but not the key. So both /articles/123?key=456 and /articles/123?key= are valid, but not /articles/123.

Edit: another requirement is that, after registering the route, I'd like to build them programatically, and I can't seem to work out how to use r.Queries even though the docs specifically state that it's possible (https://github.com/gorilla/mux#registered-urls).

@jmaloney answer works, but doesn't allow to build URLs from names.

3
@smarx I've seen that question, but there are 2 reasons why it doesn't work for me: 1. it prevents me to use mux.Vars(req)["tab"] in my Handler 2. it doesn't allow me to build registered URLs by name (I've updated the question)stassinari

3 Answers

27
votes

I would just register your handler twice.

router.Path("/articles/{id:[0-9]+}").
    Queries("key", "{[0-9]*?}").
    HandlerFunc(YourHandler).
    Name("YourHandler")

router.Path("/articles/{id:[0-9]+}").HandlerFunc(YourHandler)

Here is a working program to demonstrate. Notice that I am using r.FormValue to get the query parameter.

Note: make sure you have an up to date version go get -u github.com/gorilla/mux since a bug of query params not getting added the built URLs was fixed recently.

package main

import (
    "fmt"
    "log"
    "net/http"

    "github.com/gorilla/mux"
)

var router = mux.NewRouter()

func main() {
    router.Path("/articles/{id:[0-9]+}").Queries("key", "{key}").HandlerFunc(YourHandler).Name("YourHandler")
    router.Path("/articles/{id:[0-9]+}").HandlerFunc(YourHandler)

    if err := http.ListenAndServe(":9000", router); err != nil {
        log.Fatal(err)
    }
}

func YourHandler(w http.ResponseWriter, r *http.Request) {
    id := mux.Vars(r)["id"]
    key := r.FormValue("key")

    u, err := router.Get("YourHandler").URL("id", id, "key", key)
    if err != nil {
        http.Error(w, err.Error(), 500)
        return
    }

    // Output:
    // /articles/10?key=[key]
    w.Write([]byte(u.String()))
}
12
votes

If you register query parameters they are required doc:

All variables defined in the route are required, and their values must conform to the corresponding patterns.

Because those parameters are optional you just need to check for them inside of a handler function: id, found := mux.Vars(r)["id"]. Where found will show if the parameter in the query or not.

3
votes

Seems like the best way to handle optional URL parameters is to define your router as normal without them, then parse the optional params out like this:

urlParams := request.URL.Query()

This returns a map that contains the URL parameters as Key/Value pairs.