33

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.

user3254198
  • 763
  • 6
  • 23
stassinari
  • 543
  • 2
  • 5
  • 8
  • 1
    One approach: https://stackoverflow.com/questions/43379942/how-to-have-an-optional-query-in-get-request-using-gorilla-mux – user94559 Jul 28 '17 at 16:48
  • @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 Jul 28 '17 at 17:17

3 Answers3

34

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()))
}
user3254198
  • 763
  • 6
  • 23
jmaloney
  • 11,580
  • 2
  • 36
  • 29
  • I tried to do that, and worked, even though I don't know if it's a hack or not. Only problem is I'd like to use the registered URLs to reverse build URLs by name and handling query values using them and it doesn't seem to work. I'll update the question. – stassinari Jul 28 '17 at 17:06
  • @stassinari I think my recent edit should address your issues. – jmaloney Jul 28 '17 at 19:49
  • Thank you @jmaloney, I was able to get it to work. One question though: is there a reason why you use `r.FormValue` instead of `mux.Vars`? I seem to be able to get it to work – stassinari Jul 31 '17 at 14:36
  • Also, to be able to build URLs both with params and without, I resorted to use two different names for the routes, as with your method one name is lost, and "overloading" the routes with two identical names messes with mux and doesn't seem to work. – stassinari Jul 31 '17 at 14:38
  • 2
    @stassinari I use `r.FormValue` since it will return an empty string if the key is not found, `key` would not be found using `mux.Vars` if the request went to the second handler, handling that case also makes you have to check if the map has that value. I only "Named" the route with the query params since building a url with blank queries is still valid and results in less code and logic. For instance, in the above code if the user hit the second route the output would just be `/articles/10?key=` which is perfectly fine. – jmaloney Jul 31 '17 at 16:43
  • @stassinari this question is almost 3 years old. Is there a better way to add optional parameter without registering a handle twice nowadays? – vrybas Jun 04 '20 at 16:36
  • @vrybas I have to say I haven't looked into it, this solution worked at the time and we didn't change it. If you find any new tips, please share here or in a new answer! – stassinari Jun 05 '20 at 10:27
  • So if I have 10 optional parameters i have to declare a route for each possible combination? seems tedious.. – sgt-hartman Jun 16 '21 at 15:49
  • @sgt-hartman You could just define all 10 of your arguments in the first handler. Personally I would not use this feature of mux if I expected a bunch of optional query args. I would define the handler without any arguments and use the `url` package to construct the url query if needed. The `Queries` feature seems most useful when you want to have multiple handlers for the same route but separate them by query arguments. – jmaloney Jun 17 '21 at 16:38
  • @jmaloney Yeah, by nature query string are commonly used as optional parameters. Required parameters should feet into the URI. Would be a great add to the library if it could handle that. – sgt-hartman Jun 18 '21 at 08:01
18

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.

Community
  • 1
  • 1
Pavlo Strokov
  • 1,967
  • 14
  • 14
11

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.

Nick
  • 3,172
  • 3
  • 37
  • 49